Compare commits

..

34 Commits

Author SHA1 Message Date
d84d833694 nova funcionalidade: reconciliacao temporal 2026-09-02 13:04:56 -03:00
fe8c18093f Ajustes no template para definicao de campos para extracao 2026-09-01 20:58:51 -03:00
T3782834
f4638e287a ajuste no contas 2026-09-01 11:23:22 -03:00
T3782834
e24963abe1 ajuste no guardraild COE 2026-08-31 21:11:51 -03:00
10a6f357dd Conferencia de parametros coletados na transacao 2026-08-31 17:27:35 -03:00
c8c83981c4 Ajustes conforme relatorio de testes 2026-08-27 2026-08-29 21:04:31 -03:00
83bf961185 Ajustes conforme relatorio de testes 2026-08-27 2026-08-29 11:22:10 -03:00
81f24d7357 Ajustes conforme relatorio de testes 2026-08-27 2026-08-29 10:03:42 -03:00
1fd18531c0 Documentation organization 2026-08-27 21:11:01 -03:00
df2a1cbe1b bugfix: oci_openai provider 2026-08-27 20:30:27 -03:00
42ab000c7b Documentation organization 2026-08-27 10:28:23 -03:00
472d44074c Documentation organization 2026-08-27 09:41:46 -03:00
T3782834
faf5ca55ba new feature: Integration with kbdb Autonomous 2026-08-26 10:15:51 -03:00
ac18d68eaf new feature: External guardrails/judges 2026-08-24 11:55:07 -03:00
63d0fb51c4 new feature: External guardrails/judges 2026-08-24 11:26:08 -03:00
T3782834
a472daa1e4 adjustments: transaction parameter extraction 2026-08-21 23:06:54 -03:00
T3782834
d93efd8972 adjustments: transaction parameter extraction 2026-08-21 22:44:36 -03:00
727997aa41 Adding transaction state 2026-08-21 17:59:06 -03:00
2496e831a1 Adding transaction state 2026-08-21 16:47:13 -03:00
b5d2a12953 bugfixes: router stickness vs transaction workflow parameters 2026-08-21 14:34:30 -03:00
bdc44b15d6 bugfix: enviar subject para contexto do guardrail 2026-08-21 11:01:48 -03:00
05077a91bc bugfix: aofertas 2026-08-21 10:47:19 -03:00
19c4024693 new feature: reasoning_content for compatible models in ainvoke_response() 2026-08-21 07:40:21 -03:00
22e8f56146 bugfixes: router stickness vs transaction workflow parameters 2026-08-20 23:29:56 -03:00
0a72afb94e bugfixes: router stickness vs transaction workflow parameters 2026-08-20 22:58:25 -03:00
57df723a24 new feature: mcp pre-validation 2026-08-20 15:13:20 -03:00
6e43cc0158 adjustments: route stickness usefull tokens only 2026-08-20 13:14:57 -03:00
d271031198 adjustments: route stickness usefull tokens only 2026-08-20 11:44:24 -03:00
34d33350ae new feature: transaction evidence 2026-08-20 09:52:22 -03:00
ad778f6889 bugfix: route stickness precedences (transaction in the same intent) 2026-08-20 09:18:58 -03:00
762f6fb024 bugfixes: transaction parameter collector, generic formatting messages, guardrails, prompts. Testing contas 2026-08-20 00:23:50 -03:00
f9c66b4792 Melhorias no guardrails de FRASEOLOGY. Melhorias no Transaction Workflow 2026-08-19 16:08:03 -03:00
560e79d21b New features: Domain_Requested_LLM_Composition, Domain_Requested_RAG, Offline_Workflow_Regression, Pause_Resume_Workflow, Voice_Interruption_Replay, Workflow_Error_Recovery, Durable Idempotency, Workflow_Pause_Resume, Dynamic_Transaction_States, Post_Finalization_Replay, Retrieval_Tool_Guardrails 2026-08-19 09:25:38 -03:00
23d32bbcfc Testes de Carga: /deploy/oke/load-test 2026-08-17 18:59:14 -03:00
660 changed files with 17793 additions and 60865 deletions

View File

@@ -234,8 +234,3 @@ KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON= KBDB_METADATA_JSON=
KBDB_MIN_SCORE= KBDB_MIN_SCORE=
# Framework vocabulary and standard responses: pt-BR | en
AGENT_LOCALE=pt-BR
# Optional directory containing complete pt-BR.json and en.json catalogs.
# AGENT_LOCALES_DIR=./config/locales

View File

@@ -1,56 +0,0 @@
# Copy these settings into the .env loaded by the running backend/gateway.
# Existing feature flags (ENABLE_LLM_ROUTER, ENABLE_ROUTE_STICKINESS, etc.) still apply.
# Global default; each individual setting below takes precedence.
DECISION_PROVIDER=current
DECISION_FALLBACK_TO_CURRENT=true
JEV_ENDPOINT=https://api.typesafe.ai/v1/systemone
AI_GATEWAY_API_KEY=
JEV_MODEL=jev-latest
JEV_TIMEOUT_SECONDS=15
JEV_MIN_CONFIDENCE=0.7
JEV_DECISION_DIAGNOSTICS=true
# All options: current | jev. No existing behavior changes by default.
DECISION_ROUTE_CONTINUITY_PROVIDER=current
DECISION_ROUTER_PROVIDER=current
DECISION_WORKFLOW_EXPECTED_INPUT_PROVIDER=current
DECISION_WORKFLOW_REENTRY_PROVIDER=current
DECISION_TRANSACTION_CONFIRMATION_PROVIDER=current
DECISION_TRANSACTION_PARAMETER_RELEVANCE_PROVIDER=current
DECISION_TRANSACTION_INTENT_SHIFT_PROVIDER=current
DECISION_MULTI_INTENT_PROVIDER=current
DECISION_TRANSACTION_PARAMETER_RECONCILIATION_PROVIDER=current
DECISION_GLOBAL_SUPERVISOR_PROVIDER=current
DECISION_PROCESSING_INTERRUPTION_PROVIDER=current
# Generic LLM guardrail and output supervisor (existing profile names).
DECISION_GUARDRAIL_GUARDRAIL_PROVIDER=current
DECISION_GUARDRAIL_GRL_PROVIDER=current
# Calibrated guardrails, independently by task.
DECISION_GUARDRAIL_PINJ_PROVIDER=current
DECISION_GUARDRAIL_COER_PROVIDER=current
DECISION_GUARDRAIL_TOX_PROVIDER=current
DECISION_GUARDRAIL_REVPREC_PROVIDER=current
DECISION_GUARDRAIL_AOFERTA_PROVIDER=current
DECISION_GUARDRAIL_OOS_PROVIDER=current
DECISION_GUARDRAIL_FRASEOLOGIA_PROVIDER=current
DECISION_GUARDRAIL_RAGSEC_PROVIDER=current
DECISION_GUARDRAIL_DLEX_IN_PROVIDER=current
DECISION_GUARDRAIL_DLEX_OUT_PROVIDER=current
# Calibrated judges, by task; generic judges, by configured name.
DECISION_JUDGE_CSI_PROVIDER=current
DECISION_JUDGE_VCTN_PROVIDER=current
DECISION_JUDGE_ALUC_PROVIDER=current
DECISION_JUDGE_RQLT_PROVIDER=current
DECISION_JUDGE_LLM_JUDGE_PROVIDER=current
# Optional threshold per point, independent from the provider choice.
# DECISION_ROUTE_CONTINUITY_MIN_CONFIDENCE=0.90
# DECISION_TRANSACTION_CONFIRMATION_MIN_CONFIDENCE=0.95
# Named calibrated judge overrides its task group (optional).
# DECISION_JUDGE_GROUNDEDNESS_PROVIDER=jev
# DECISION_JUDGE_RESPONSE_QUALITY_PROVIDER=current
# Optional individual guardrail recheck override.
# DECISION_GUARDRAIL_DLEX_OUT_PROTOCOL_AUTHORIZATION_RECHECK_PROVIDER=jev

133
.idea/workspace.xml generated
View File

@@ -47,108 +47,27 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;ModuleVcsDetector.initialDetectionPerformed&quot;: &quot;true&quot;, "ModuleVcsDetector.initialDetectionPerformed": "true",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager&quot;: &quot;true&quot;, "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;, "RunOnceActivity.git.unshallow": "true",
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, "RunOnceActivity.typescript.service.memoryLimit.init": "true",
&quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;, "SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "git-widget-placeholder": "main",
&quot;Shell Script.agent_frontend.executor&quot;: &quot;Run&quot;, "kotlin-language-version-configured": "true",
&quot;Shell Script.agent_template_backend.executor&quot;: &quot;Run&quot;, "last_opened_file_path": "D:/Dropbox/ORACLE/TIM/FY27/Commits/agent_platform_oci",
&quot;Shell Script.mcp_gateway.executor&quot;: &quot;Run&quot;, "node.js.detected.package.eslint": "true",
&quot;Shell Script.mcp_server.executor&quot;: &quot;Run&quot;, "node.js.detected.package.tslint": "true",
&quot;git-widget-placeholder&quot;: &quot;main&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;last_opened_file_path&quot;: &quot;D:/Dropbox/ORACLE/TIM/FY27/Commits/agent_platform_oci&quot;, "nodejs_package_manager_path": "npm",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "settings.editor.selected.configurable": "configurable.group.appearance",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "vue.rearranger.settings.migration": "true"
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;project.structure.last.edited&quot;: &quot;Project&quot;,
&quot;project.structure.proportion&quot;: &quot;0.15&quot;,
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;advanced.settings&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}</component> }]]></component>
<component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS">
<recent name="C:\Asus_Projects\kbdb\agent_framework_oci\templates\agent_template_backend" />
</key>
</component>
<component name="RunManager" selected="Shell Script.agent_template_backend">
<configuration name="agent_frontend" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="wsl bash -lc &quot;source .venv/bin/activate &amp;&amp; cd apps/agent_frontend &amp;&amp; python -m http.server 5173&quot;" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="powershell.exe" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
<configuration name="agent_template_backend" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="wsl bash -lc &quot;source .venv/bin/activate &amp;&amp; cd templates/agent_template_backend &amp;&amp; python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload&quot;" />
<option name="INDEPENDENT_SCRIPT_PATH" value="false" />
<option name="SCRIPT_PATH" value="$PROJECT_DIR$/templates/agent_template_backend/app" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="false" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="powershell.exe" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
<configuration name="mcp_gateway" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="wsl bash -lc &quot;source .venv/bin/activate &amp;&amp; cd scripts &amp;&amp; bash run_mcp_gateway.sh&quot;" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="powershell.exe" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
<configuration name="mcp_server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="wsl bash -lc &quot;source .venv/bin/activate &amp;&amp; cd scripts &amp;&amp; bash run_mcp_servers.sh&quot;" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="powershell.exe" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
<list>
<item itemvalue="Shell Script.agent_template_backend" />
<item itemvalue="Shell Script.mcp_gateway" />
<item itemvalue="Shell Script.mcp_server" />
<item itemvalue="Shell Script.agent_frontend" />
</list>
</component>
<component name="SharedIndexes"> <component name="SharedIndexes">
<attachedChunks> <attachedChunks>
<set> <set>
@@ -173,14 +92,10 @@
<workItem from="1785245181314" duration="79000" /> <workItem from="1785245181314" duration="79000" />
<workItem from="1785245608367" duration="670000" /> <workItem from="1785245608367" duration="670000" />
<workItem from="1785326458404" duration="86000" /> <workItem from="1785326458404" duration="86000" />
<workItem from="1785405876905" duration="1300000" /> <workItem from="1785414225783" duration="148000" />
<workItem from="1785409583147" duration="1561000" /> <workItem from="1785414447653" duration="704000" />
<workItem from="1785452072692" duration="784000" /> <workItem from="1785630146329" duration="316000" />
<workItem from="1785453511270" duration="1006000" /> <workItem from="1787832640995" duration="4662000" />
<workItem from="1792665278602" duration="734000" />
<workItem from="1793193595355" duration="436000" />
<workItem from="1793194079956" duration="5443000" />
<workItem from="1793271962068" duration="758000" />
</task> </task>
<task id="LOCAL-00001" summary="Ajustes na documentação e remanejamento dos folders"> <task id="LOCAL-00001" summary="Ajustes na documentação e remanejamento dos folders">
<option name="closed" value="true" /> <option name="closed" value="true" />

View File

@@ -1,57 +0,0 @@
# Atualização dos testes após mudanças de contrato
Esta revisão mantém os quatro nomes de arquivo e a quantidade de testes. Oito
testes foram atualizados, sem `skip`, `xfail` ou alterações no runtime para
acomodar as expectativas antigas. Os nomes de sete funções foram ajustados para
descrever o comportamento atual. Os comentários nos testes registram o histórico.
## Histórico verificado no Git do pacote original
- `1711575`, 2026-09-07, `bugfix reconciliation extractor`: removeu
`field_evidence_views`, a construção de candidatos estruturais e partes da
reconciliação anterior.
- `82636ec`, 2026-09-08, `bugfix: extractor cases 8 and 16`: consolidou a coleta
de parâmetros no `single_extractor`, com contexto e pré-validação autoritativa.
- `36f9781`, 2026-09-18, `new feature: transaction interruption for same intent replacement`:
também adicionou sanitização e reavaliação ao DLEX_OUT e passou a usar
`sanitized_text` somente quando há um texto substituto.
## O que os testes passam a verificar
| Arquivo | Contrato coberto |
|---|---|
| `tests/test_transactional_tool_flow.py` | Após `NEEDS_PARAMETER`, o fluxo aguarda nova informação; a nova validação ocorre no turno seguinte e a operação não executa antes da confirmação. |
| `tests/test_transaction_parameter_llm_precedence.py` | O reconciliador recebe schema e contexto, aceita o campo resolvido e respeita `preserve` sem sobrescrever o valor conhecido. |
| `tests/test_transaction_explicit_abandonment_and_structural_candidate.py` | Campo indefinido continua pendente; extração contextual bem-sucedida passa pela validação antes da confirmação; `clear` não inventa um valor nem apaga os demais campos. |
| `tests/test_dlex_out_expected_protocol_authorization.py` | Protocolo autorizado não exige substituição do texto; credencial é removida e a resposta é reavaliada; outra violação mantém o bloqueio. |
Os simuladores deixaram de depender de frases específicas do prompt, como
“FOCO PRINCIPAL DA RECONCILIAÇÃO”. Eles devolvem saídas compatíveis com o contrato
da chamada. A identificação semântica por um modelo real não é comprovada por
esses testes unitários: os testes verificam como o framework utiliza suas saídas.
## Verificações realizadas
- 160 testes selecionados aprovados no pacote atualizado, incluindo os 41 testes
de localização e os quatro arquivos desta revisão.
- Os 66 testes dos quatro arquivos também foram executados usando a biblioteca
original do ZIP: todos aprovados. Assim, a atualização dos testes não depende
da alteração de idiomas para passar.
- Contagem mantida por arquivo: 31, 13, 17 e 5 testes, respectivamente.
- Nenhuma chamada a OCI, modelos remotos ou ferramentas reais de negócio.
- Esta revisão não afirma que a suíte completa do repositório foi executada.
## Aplicar ao repositório
O ZIP completo inclui a localização em `pt-BR` e `en` entregue anteriormente e
esta revisão dos testes. Copie o conteúdo da pasta `agent_platform_oci` para a
raiz do repositório, preservando a pasta `.git` que já existe no seu checkout.
Se a localização já estiver aplicada, o ZIP complementar de testes contém apenas
os quatro arquivos atualizados e esta documentação. Não é preciso usar ambos.
Sugestão de mensagem de commit para a revisão dos testes:
```text
test: align transaction and DLEX_OUT tests with current contracts
```

View File

@@ -77,12 +77,3 @@ No compose, o backend usa `config/mcp_servers.docker.yaml` para apontar para `te
## Operações read-only e transacionais ## Operações read-only e transacionais
Use `config/tool_policies.yaml` no backend para classificar somente as operações que precisam de tratamento adicional. A validação é aplicada no roteador central antes do MCP Gateway/Server. O arquivo é opcional e templates antigos continuam usando as políticas já presentes em `tools.yaml`. A configuração completa e o roteiro de migração estão em [README_TOOL_POLICIES.md](README_TOOL_POLICIES.md). Use `config/tool_policies.yaml` no backend para classificar somente as operações que precisam de tratamento adicional. A validação é aplicada no roteador central antes do MCP Gateway/Server. O arquivo é opcional e templates antigos continuam usando as políticas já presentes em `tools.yaml`. A configuração completa e o roteiro de migração estão em [README_TOOL_POLICIES.md](README_TOOL_POLICIES.md).
## Chamadas MCP multi-item
Para operações com listas de produtos, pedidos, ativos ou outros recursos, **não implemente loops de chamadas MCP dentro do agente**. O framework possui contrato genérico para parâmetros `array/list`, pre-validation, confirmação e resultado por item (`SUCCESS`, `PARTIAL_SUCCESS`, `FAILED`).
Guia completo: [MCP Multi-item — Guia do Desenvolvedor](../docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md).
A tool primária deve retornar `results[]` com `success` ou `ok` booleano por item. Em workflows, use preferencialmente `output[tool_name].results`.

View File

@@ -1,84 +0,0 @@
# Seleção individual de decisões: modelo atual ou JEV
Implementação opt-in para o projeto do ZIP de 03/11/2026. O padrão continua sendo `current`; nenhum modelo foi ativado e nenhuma chave foi incluída.
## Configuração
Copie as variáveis de `.env.jev.example` para o `.env` utilizado pelo processo. Se usar contêiner/Kubernetes, passe as variáveis também para o ambiente do processo e guarde a chave no mecanismo de secrets do ambiente. Reinicie o serviço após alterar a configuração. Substitua os arquivos de código e refaça o build/redeploy caso o framework esteja instalado em pacote ou imagem; reiniciar sem atualizar o pacote instalado não aplica mudanças de código.
Exemplo: ativar somente continuidade e intenção, mantendo o restante atual:
```dotenv
DECISION_PROVIDER=current
DECISION_ROUTE_CONTINUITY_PROVIDER=jev
DECISION_ROUTER_PROVIDER=jev
AI_GATEWAY_API_KEY=<sua chave>
JEV_MODEL=typesafe-ai/jev
DECISION_FALLBACK_TO_CURRENT=true
JEV_MIN_CONFIDENCE=0.7
DECISION_ROUTE_CONTINUITY_MIN_CONFIDENCE=0.90
```
A variável individual prevalece sobre `DECISION_PROVIDER`. Uma variável individual ausente herda o padrão global. Como o exemplo define todas como `current`, mudar somente a variável global não altera os pontos explicitamente definidos. Os valores válidos são `current` e `jev`.
A seleção não habilita funcionalidades desativadas: `ENABLE_LLM_ROUTER`, `ENABLE_ROUTE_STICKINESS`, configuração de semantic classifiers e configuração dos guardrails/judges continuam controlando se cada ponto é executado. Para não tornar `current` incompatível, não foram alteradas as regras determinísticas nem os controles de execução.
## Pontos disponíveis
Cada sufixo abaixo corresponde a `DECISION_<SUFIXO>_PROVIDER`:
| Sufixo | Função |
|---|---|
| ROUTE_CONTINUITY | CONTINUE, ROUTE, HUMAN_HANDOFF, END_SESSION; inclui sondagem de controles globais que usa o mesmo classificador |
| ROUTER | Intenção semântica; agente, domínio e ferramentas derivados do catálogo local |
| WORKFLOW_EXPECTED_INPUT | Opção permitida para retomada de workflow; opção sem correspondência não retoma |
| WORKFLOW_REENTRY | REENTER ou RESUME |
| TRANSACTION_CONFIRMATION | Confirmação semântica, usando as opções configuradas; parser explícito permanece |
| TRANSACTION_PARAMETER_RELEVANCE | RELEVANT ou NOT_RELEVANT |
| TRANSACTION_INTENT_SHIFT | CONTINUE, REPLACE, SHIFT ou ABANDON e intenção de destino |
| MULTI_INTENT | Intenção de cada objetivo extraído, com preservação de ordem e trechos literais |
| TRANSACTION_PARAMETER_RECONCILIATION | resolved, preserve, clear ou unresolved por parâmetro |
| GLOBAL_SUPERVISOR | Backend permitido no registro do supervisor global |
| PROCESSING_INTERRUPTION | Regenerar resposta de voz ou repetir |
| GUARDRAIL_GUARDRAIL / GUARDRAIL_GRL | Guardrails LLM genéricos com os perfis padrão; perfis personalizados usam GUARDRAIL_<PERFIL> |
| GUARDRAIL_PINJ, COER, TOX, REVPREC, AOFERTA, OOS, FRASEOLOGIA, RAGSEC, DLEX_IN, DLEX_OUT | Cada tarefa calibrada tem sua própria variável GUARDRAIL_<TAREFA> |
| JUDGE_CSI, JUDGE_VCTN, JUDGE_ALUC, JUDGE_RQLT | Cada tarefa de judge calibrado, como padrão de grupo |
| JUDGE_<NOME> | Judge genérico, usando seu `name`; o padrão é JUDGE_LLM_JUDGE |
Um judge calibrado também aceita `DECISION_JUDGE_<NOME_CONFIGURADO>_PROVIDER`, que prevalece sobre o grupo da tarefa (por exemplo `DECISION_JUDGE_GROUNDEDNESS_PROVIDER` prevalece sobre `DECISION_JUDGE_ALUC_PROVIDER`). Rechecagens de guardrail aceitam o nome completo do componente, por exemplo `DECISION_GUARDRAIL_DLEX_OUT_PROTOCOL_AUTHORIZATION_RECHECK_PROVIDER`, prevalecendo sobre `DECISION_GUARDRAIL_DLEX_OUT_PROVIDER`.
Caracteres diferentes de letras/números são convertidos em `_`, com nome em maiúsculas. O mesmo ponto pode definir `DECISION_<SUFIXO>_MIN_CONFIDENCE`; esse valor é independente dos limites já existentes no componente. Os limites existentes continuam sendo aplicados depois da integração.
## O que é híbrido
- Multi-intenção: o modelo atual extrai os objetivos e seus trechos, mantendo a ordem. O JEV escolhe a intenção de cada objetivo. Não há geração de trechos pelo JEV; trechos não literais são rejeitados. Isso pode custar mais que uma chamada atual isolada, por ser uma etapa adicional.
- Reconciliação: o modelo atual extrai candidatos e proveniência. JEV decide o tratamento de cada campo. A decisão não inventa valores e não substitui validação de negócio.
- Geração final, extração livre, resumos, RAG rewrite/compression, TOXOUT e FALLBACK de texto seguem no provedor atual. Autorização, alçadas, idempotência, pré-validação de negócio e execução seguem nas implementações existentes.
- Guardrail LLM genérico no JEV avalia se o texto original pode passar sem alteração. Se precisar de sanitização, deve bloquear; o JEV não fornece `sanitized_text` ou instruções textuais de reescrita. Reescritas já separadas no fluxo continuam generativas.
- Judges JEV não inventam rótulos ou justificativas de prosa: o retorno contém os campos necessários à decisão e motivo técnico fixo. RQLT/CSI usam score 0–10; judge genérico converte para 0–1. Os limites precisam de calibração antes de produção.
## Transporte, falhas e observabilidade
O cliente usa `POST https://ai-gateway.vercel.sh/v1/decisions` com `typesafe-ai/jev`, API key em `Authorization`, timeout de 15 segundos por padrão e perguntas choice/predicate/score. Usa a biblioteca padrão do Python; não adiciona dependências de produção. A chamada bloqueante roda em thread para não bloquear o event loop.
Respostas incompletas, tipos incompatíveis, opções não declaradas, números inválidos, confiança abaixo do limite e erros de transporte acionam o provedor atual uma vez quando `DECISION_FALLBACK_TO_CURRENT=true`. Sem fallback, a exceção chega ao tratamento de falha já existente do componente; isso não redefine automaticamente cada política fail-open/fail-closed. Confiança ausente em choice/score é zero e aciona fallback no limite padrão. Para predicates usa-se a probabilidade do resultado selecionado. Para várias perguntas usa-se o mínimo das confianças.
O custo de uma chamada JEV seguida de fallback inclui ambas as etapas. Respostas recebidas são contabilizadas no repositório de uso e no evento `decision.jev` quando o provedor original oferece esses recursos, mesmo quando a validação rejeita a resposta. Logs registram ponto, modelo, duração, uso e falha/fallback, sem imprimir prompts, chave ou corpo de erro HTTP. Não há geração Langfuse de chat para chamadas JEV; a integração publica evento de decisão.
## Validação
Teste isolado, sem dependências do projeto ou API real:
```sh
python3 tests/test_jev_transport_standalone.py
```
Testes adicionais de integração em ambiente com dependências instaladas:
```sh
PYTHONPATH=libs/agent_framework/src python -m pytest tests/test_jev_decisions.py tests/test_expected_input_semantic_classifier.py tests/unit/test_semantic_route_stickiness.py
```
O teste isolado cobre contratos, seleção, precedência de configuração, fallback, erros, confiança, transporte e análise sintática dos módulos. Resultado local: 17 testes isolados passaram, incluindo execução dos corpos reais de continuidade, roteamento, reconciliação e judge calibrado com transporte simulado. A suíte completa não foi executada: as dependências do projeto não estavam disponíveis e a tentativa de instalação não conseguiu obtê-las. A suíte completa e a qualidade/latência do modelo real precisam ser verificadas no ambiente de execução. Não foi feita chamada real ao JEV.
Fontes de protocolo consultadas: https://vercel.com/docs/ai-gateway/sdks-and-apis/openai-decisions e https://vercel.com/ai-gateway/models/jev.

View File

@@ -1,264 +0,0 @@
The Universal Permissive License (UPL), Version 1.0
This page provides a copy of and also attempts to answer some frequently asked questions about the Universal Permissive License version 1.0. The UPL was first drafted by Oracle in the Spring of 2014, was revised several times after extensive consultation with the Java Community Process Executive Committee and through the Open Source Initiative review process, and was finally approved by the Open Source Initiative in February 2015.
To begin with, this is the final approved text of the Universal Permissive License v 1.0:
Copyright (c) [year] [copyright holders]
The Universal Permissive License (UPL), Version 1.0
Subject to the condition set forth below, permission is hereby granted to any
person obtaining a copy of this software, associated documentation and/or data
(collectively the "Software"), free of charge and under any and all copyright
rights in the Software, and any and all patent rights owned or freely
licensable by each licensor hereunder covering either (i) the unmodified
Software as contributed to or provided by such licensor, or (ii) the Larger
Works (as defined below), to deal in both
(a) the Software, and
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
one is included with the Software (each a "Larger Work" to which the Software
is contributed by such licensors),
without restriction, including without limitation the rights to copy, create
derivative works of, display, perform, and distribute the Software and make,
use, sell, offer for sale, import, export, have made, and have sold the
Software and the Larger Work(s), and to sublicense the foregoing rights on
either these or other terms.
This license is subject to the following condition:
The above copyright notice and either this complete permission notice or at
a minimum a reference to the UPL must be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
When referring to the license, you may link to it at https://oss.oracle.com/licenses/upl or you may also find a copy on the Open Source Initiative web site at https://opensource.org/licenses/UPL.
Frequently Asked Questions
What is the UPL and why did you create it?
What are the most important features of the UPL?
Can you point to some concrete problems the UPL solves that you could not have addressed with the MIT, BSD or Apache licenses?
So why is the absence of an express patent license in the MIT or BSD licenses a problem - isn't the implied license enough?
Why didn't you include a defensive patent termination provision?
I heard this licenses our entire patent portfolio to Oracle, is that right?
How should I apply the UPL to my code?
What goes in the Larger Works file and what should it look like?
Can someone else change my Larger Works file and license my patents for other things?
What is the UPL and why did you create it?
The UPL is a highly permissive license, including both copyright and patent licenses, which permits use and relicensing under both copyleft and commercial terms, and also facilitates use as a contributor license agreement.
It was originally borne out of a discussion in the Java Community Process around what it would take to permit collaboration on JSR reference implementations in open source forges without the necessity of collaborators signing the JSPA or an Oracle Contributor Agreement. In order to accommodate the broadest possible use case, the license needed to include an express patent license and to be broadly agreed to be GPLv2, GPLv2+Classpath Exception, CDDL, and commercial license compatible, including the ability to cut and paste code between modules, and while it is almost universally agreed among those knowledgeable in FOSS licensing that license proliferation is something to be avoided without good cause, since a license meeting these relatively simple and obvious criteria did not exist, it was decided to draft one.
Drafting started with the MIT license, and after a lot of discussion with other open source lawyers, developers, members of the Java Community Process Executive Committee, and reviewers and board members of the Open Source Initiative, and several rounds of revision, finally arrived at the vetted license text you see here. The license was approved by the OSI as conforming with the Open Source Definition and non-duplicative of existing permissive licenses in February 2015.
Return to FAQ.
What are the most important features of the UPL?
The most important features and things to understand about the UPL are the following:
Clear patent protection. The UPL is a broad permissive license including both a copyright license and an express patent license, covering at least a version licensed by someone under the license (for example a distributor) and/or a version someone contributed to even if they never distribute the whole. (The reason the latter is needed is discussed below.) By virtue of the unambiguous patent license, the UPL is materially clearer with respect to the rights licensed and likely broader than either the MIT or BSD licenses.
Clear & simplified relicensing. The UPL expressly permits sublicensing under either the UPL or under other terms, which clearly allows someone to relicense code received under the UPL either on copyleft terms, on proprietary terms, or otherwise, thus permitting maximum flexibility in reuse.
Reduced overhead in source files. The UPL expressly permits use of the license without including a full copy of the text, which is useful for JavaScript or other cases where minimizing space consumed by licenses is desired - see below for our recommendation of how to apply the license or include proper attribution without the full text.
It can be used as a contributor agreement. Finally, the UPL may be used as a contributor license agreement licensing both the software itself and also contributor patents for use in one or more "Larger Works." The Larger Works licensed in this fashion are designated by the use of a separate file accompanying the license, akin to the NOTICE file that accompanies the Apache License, Version 2.0. The Larger Works file can be used to control for both contributions to other works (for example, we could specify MySQL in a Larger Works file for a work, which would then license contributor patents for MySQL as well as the contribution), to set patent license scope for specific versions (for example, we could specify the approved reference implementation of JSR-xxx including Maintenance Releases to ensure that all contributors to an RI are licensing both the final version of the RI and qualified updates under the JCP program), or both.
Return to FAQ.
Can you point to some concrete problems the UPL solves that you could not have addressed with the MIT, BSD or Apache licenses?
The most important problem the UPL solves is the inclusion of an express patent license in a license that is both broadly agreed to be uniformly copyleft compatible and also permits the software to be freely used in commercially licensed software (i.e., without concerns about reciprocal license obligations).
The MIT and BSD licenses do not include express patent licenses, and while many argue that there is an implied patent license, there is nontrivial debate about the existence & scope of any such implied license. This is addressed in some more detail below. The Apache license, on the other hand, is argued by some not to be GPLv2 compatible, which makes it tricky to use in connection with GPLv2 licensed code - the GPLv2 is still by far the most popular open source license, and unambiguous, undisputed GPL compatibility was a crucial qualifier for any license considered for use in the JCP. (Even the BSD license has been argued not to be GPL compatible - while this need not be addressed here, and it is not the conclusion of the FSF or most others, by virtue of expressly permitting relicensing on other terms, the UPL is definitively and fully GPLv2 compatible.)
Another problem solved with the UPL, which seems like a small thing but adds compliance and clarity, is that licensors and distributors are expressly permitted to reference the UPL rather than including a complete copy of the text in each item. What is the license solving for here? The MIT and BSD licenses expressly require that you include a complete copy of the license in each piece of source code. This means that if you have 50 different JavaScript files being downloaded from a page, each one should be giving you a complete copy of the license - you may be using as much bandwidth for licenses as you do for the actual code! And if you do not include a complete copy of the license, as some folks do not, merely referring to the license by URL or name, then you are not really applying the license in the intended manner and are in fact suggesting that downstream recipients not strictly follow those terms either.
Finally, the inclusion of the Larger Works concept facilitates use as a contributor license agreement (CLA), with contributors granting a patent license to the works to which they're contributing, and therefore creating a common & safe platform for collaboration where no one is going to assert infringement by the same project in which they're an active participant.
Return to FAQ.
So why is the absence of an express patent license in the MIT or BSD licenses a problem - isn't the implied license enough?
Some authorities think it is enough, but not everyone agrees on this point. To begin with, it has been suggested that in some jurisdictions there may be no implied license at all, or only a license to people to whom a licensor directly distributed the software.
But even if one were to take the position that a patent license covering any contribution is implied, and further that it covers the entirety of a work you distribute, in our world of cloud offerings, many contributors may never distribute the entirety of the work at all. This may reduce the likelihood that a court would find them to have granted an implied license for the whole of the work.
The UPL attempts to solve these problems by having each party that either contributes to the project or distributes it under the UPL expressly license all patents that they own or can freely license that cover the work - thus, e.g., licensing the whole even from contributors who never distribute the whole.
Return to FAQ.
Why isn't there a defensive patent termination provision?
A defensive patent termination provision may sound like a great idea at first, especially for a pure open source project, but does not really work for contributions to software that is also licensed on commercial terms.
Why? A company that takes on commercial obligations with respect to a piece of code needs to retain the ability to keep supporting and shipping that code. A defensive patent termination provision allows someone who makes a contribution and then breaches the project license to the project to then potentially effectively block infringement suits against them, even when they have willfully and flagrantly disregarded community norms and IP rights in the project. Such a provision is therefore in reality as much of a sword as it is a shield.
Say, for example, that a fictional company WidgetCo were to make a contribution to fictional company GadgetCo's product GadgetX under a license with a patent termination provision. Now, WidgetCo decides to violate GadgetCo's terms and distribute GadgetX without a license. GadgetCo's options for stopping the violation would ordinarily include both copyright and also patent infringement claims for any patents covering GadgetX. If, on the other hand, WidgetCo has made patent covered contributions under an agreement with a so-called defensive termination clause, GadgetCo's ability to stop WidgetCo from violating the license may be significantly compromised because it can no longer bring patent claims without risking losing its own license to the patents on the contributions, which therefore might require it to stop shipping GadgetX immediately and stop supporting versions that customers are relying on.
As a commercial distributor of software, GadgetCo's customers rely on it to have the rights it needs to be able to continue to deliver and support the software, and defensive patent termination rights in contributions are inconsistent with those obligations. Defensive patent termination only helps someone who may be subject to infringement litigation by someone who is using the code - it won't stop patent trolls, who aren't normally using the code to begin with. So who does it stop, in reality? Someone who is using the code but has IP rights that someone else using the code is infringing. Now, it may be reasonable to tell someone who has no relationship to a community that they cannot simultaneously expect to be able to use the code and also sue for its use - thus making such provisions at least not entirely crazy for a pure FOSS project that no one is signing up to develop or support on a commercial basis, and which is rarely if ever going to need to enforce its rights anyway. But for anyone who both actually expects to need to enforce its license and also wants to take contributions, this makes no sense - you need to be able to enforce your rights in the project without needing to stop shipping or using it, and accepting contributions under terms with a patent termination provision does not satisfy this basic need.
Return to FAQ.
I heard this licenses our entire patent portfolio to Oracle, is that right?
No, it is not, this is simply untrue except in odd and contrived circumstances that would apply to other FOSS licenses as well. If your entire patent portfolio happens to read on a UPL licensed project that you have distributed on those terms or contributed to, or its designated Larger Works, then yes, it would all be licensed for use in that Software and those specified Larger Works, not just to Oracle but to all licensees under the UPL, just as it would all be licensed for a project under the Apache Software License or Eclipse Public License if your entire portfolio read on a project under the ASL or EPL. Patents that do not read on the project as you contributed to or distributed it or the specified Larger Works are not licensed, there is no magic here.
The patent scope is not really all that complicated. It covers your contributions, the project you contributed to in the state it existed when you contributed, and anything specified in the Larger Works file (which may be another application, it may be other or later versions, it may be a piece of hardware, or any combination of those things - or, just as importantly, it may be none of those things, since many projects may have no Larger Works file at all, in which case the patent license is little different than the express patent license in the Apache license, only without the so-called defensive termination provision).
Return to FAQ.
How should I apply the UPL to my code?
It is best to insert a copyright notice and a copy of the license at the top of each source file, and if there is a package of numerous files, to also insert a LICENSE file including a copy of the UPL in the root of the distribution. Here is an example:
/*
** MyWidget version 1.0.
**
** Copyright (c) 2015 WidgetCo, Inc.
**
** The Universal Permissive License (UPL), Version 1.0
**
** Subject to the condition set forth below, permission is hereby granted to any
** person obtaining a copy of this software, associated documentation and/or data
** (collectively the "Software"), free of charge and under any and all copyright
** rights in the Software, and any and all patent rights owned or freely
** licensable by each licensor hereunder covering either (i) the unmodified
** Software as contributed to or provided by such licensor, or (ii) the Larger
** Works (as defined below), to deal in both
**
** (a) the Software, and
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
** one is included with the Software (each a "Larger Work" to which the Software
** is contributed by such licensors),
**
** without restriction, including without limitation the rights to copy, create
** derivative works of, display, perform, and distribute the Software and make,
** use, sell, offer for sale, import, export, have made, and have sold the
** Software and the Larger Work(s), and to sublicense the foregoing rights on
** either these or other terms.
**
** This license is subject to the following condition:
** The above copyright notice and either this complete permission notice or at
** a minimum a reference to the UPL must be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE.
*/
If using the UPL for JavaScript or other circumstances in which space is at a premium, you may use a reference to the license rather than a full copy (though a full copy is always preferred if there's room and you won't be transmitting a bunch of copies to the same user). If you want to use just a short reference, you should still insert a copyright notice, and along with it, a statement that the file is licensed under the UPL, and may link to it at https://oss.oracle.com/licenses/upl or on the Open Source Initiative web site at https://opensource.org/licenses/UPL.
Here is an example header with a short reference for use in source code:
/*
** MyWidget version 1.0.
**
** Copyright (c) 2015 WidgetCo, Inc.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
Return to FAQ.
What goes in the Larger Works file and what should it look like?
To begin with, we should note that the Larger Works concept will be irrelevant to many people and to be clear, there is no need to license any Larger Works at all in order to use the UPL on your code - this is still advantageous versus other permissive licenses because licensees can count on a patent license of unambiguous scope from each contributor, can freely relicense the code as necessary for their needs, and can abbreviate the license notice if appropriate.
The purpose of the Larger Works addition to the license is to allow you to use the UPL as a contributor license agreement, commonly referred to as a CLA, licensing patents for a specified larger work or set of works with which contributions may be used. If you desire to use the UPL as a CLA, in addition to including the UPL and/or references to it in your source, you should insert a lrgrwrks.txt file in the root of the distribution specifying the licensed Larger Works. You will name the licensed project/product there, and if appropriate, specify what versions are covered. In theory you could use a single word naming a product, which would be effective, but some slightly prettier examples are as follows.
Here is what we are hoping to use in connection with JCP Reference Implementations:
/* Larger Works File for JSR-xxx
**
** Licensed Larger Works: The Reference Implementation for JSR-xxx Including Maintenance Releases
** as defined in the JCP Procedures documentation.
*/
So what is the intent here? We are not expanding the scope of the licensed project beyond the RI, however, we want to make sure that the RI is fully covered such that an early contributor who stops adding new code still licenses the RI as a whole, including Maintenance Releases down the line.
A somewhat broader example is as follows:
/* Larger Works File for MyWidget
**
** Larger Works: MyApp
*/
Here, someone has set up the Larger Works file to act as a CLA for the application MyApp. The licensors of MyWidget are also licensing their patents for use in MyApp, and because there is no version restriction, this is presumably true for future versions of MyApp as well as the current version. If they wanted to license only the current version, they could have said:
/* Larger Works File for MyWidget
**
** Larger Works: MyApp 1.0
*/
Or if Company A wanted to use the UPL with a Larger Works file as a CLA for all of their products, they could use one of the following:
/* Larger Works File for Contributions to Company A Projects
**
** Larger Works: All Software Authored or Distributed by Company A
*/
or
/* Larger Works File for Contributions to Company A Projects
**
** Larger Works: All Company A Products and Projects
*/
If the Apache Software Foundation wanted to use the UPL as a CLA, they could use the following:
/* Larger Works File for Contributions to ASF Projects
**
** Larger Works: All Apache Software Foundation Projects
*/
or even more broadly, to include dependencies that are not ASF projects:
/* Larger Works File for Contributions to ASF projects
**
** Larger Works: All Apache Software Foundation projects and their dependencies
*/
or they could make it project specific, for single or multiple Projects:
/* Larger Works File for Contributions to Apache Ant & Maven
**
** Larger Works: Apache Ant and Apache Maven and their subprojects
*/
or project and version specific:
/* Larger Works File for Contributions to Cassandra
**
** Larger Works: Apache Cassandra 2.0.15 and updates other than major version updates
*/
or subject area specific:
/* Larger Works File for Contributions to Build Tools
**
** Larger Works: All Apache Software Foundation projects pertaining to software builds and related processes, including without
** limitation Ant, Ant Libraries, Ivy, IvyDE, EasyAnt, Maven and other software for controlling build processes tools
*/
As you can see, the intent of the Larger Works file is to permit nearly infinite flexibility in what is being licensed beyond the Software itself, as every company and FOSS project has different needs. Note that while a comment format has been used for these examples, since this is not executable code, this isn't strictly necessary, it was done as a matter of habit and in case someone imports one of the files into some executable code.
Return to FAQ.
Can someone else change my Larger Works file and license my patents for other things?
To begin with, a party who alters the Larger Works file cannot alter the scope of the license someone else granted, any more than they could alter the license text of the UPL, the GPL, or any other license and assert that the original licensors are bound by their changes. So no one can offer a broader license to your patents than the one you declared. The only power they have is to sublicense downstream recipients on different terms under whatever IP rights they have.
And on the flip side, what happens if someone simply removes the Larger Works file from a code base and then redistributes without it? Then they are sublicensing the code to a downstream recipient on other terms, without any Larger Works being licensed under their own patents, though the license from the original licensors would still survive on those terms as to people who receive the Software from other sources.
Similarly, if someone alters the Larger Works file and then distributes the code to new recipients themselves under the UPL, then that new distributor is granting a license of a different scope than the one granted by the original authors, which they are permitted to do, just as they would be permitted to grant their own license to the code under the GPL or a commercial license. However, the polite thing to do in this case would be to address the altered scope of their license in the Larger Works file while leaving intact the Larger Works designation from prior licensors. Let's say, for example, we start off with a Larger Works file that looks like this, covering all of Java:
/* Larger Works File for Contributions to Project X
**
** Larger Works: All Oracle Java implementations including without limitation Oracle Java EE, Java SE, Java ME, and OpenJDK
*/
This would grant a license to all of Java from each contributor on those terms. Now let's say Company A only wants to license a particular JSR, not all of Java. They would ideally add separate license and Larger Works files that travel with their contributions calling out only that JSR, most effectively by placing them in a separate directly clearly demarcating what is under that license. (Separating these things out is always a trick when you try to commingle source in the same file, but if you keep files separate, this is not generally difficult.)
In the alternative, if the contributions were not easily separable from the larger project in another directory, and for some reason they could not add a separate directory just for the license either, and therefore absolutely needed a way to combine their own, distinct Larger Works designation with that from others, they could in theory also add their own declaration to the existing Larger Works file without purporting to change the license from others as follows:
/* Larger Works File for Contributions to Project X
**
** Larger Works: All Oracle Java implementations including without limitation Oracle Java EE, Java SE, Java ME, and OpenJDK
**
** Exception: Company A licenses only the following Larger Works: The Reference Implementation for JSR-xxx including
** Maintenance Releases as defined in the JCP Procedures documentation
*/
This would therefore clearly inform a recipient that only the RI was licensed under Company A's patents, but leave intact the reference to the broader license from other contributors.
There are certainly other possibilities for accommodating multiple Larger Works scopes as well, about which your counsel can advise you.
Return to FAQ.
Return to top.

View File

@@ -1,133 +0,0 @@
# Termos e frases configuráveis — pt-BR e en
Esta distribuição inclui dois catálogos completos, em JSON UTF-8:
- `libs/agent_framework/src/agent_framework/locales/pt-BR.json`
- `libs/agent_framework/src/agent_framework/locales/en.json`
Cada arquivo reúne termos reconhecidos, mensagens padronizadas, instruções de
recuperação de guardrails e padrões de extração. JSON permite editar o catálogo
sem uma dependência adicional no carregador. Os dois arquivos usam as mesmas chaves.
## Selecionar o idioma
No `.env` do backend:
```dotenv
AGENT_LOCALE=en
```
Para português, use `AGENT_LOCALE=pt-BR` ou omita a variável. Também são aceitos
`pt`, `pt_BR`, `en-US` e `en-GB`. Uma opção inexistente gera erro; não há troca
silenciosa para português quando o idioma solicitado não está disponível.
O idioma é o padrão do backend. Os prompts e as configurações de negócio dos
agentes continuam sendo definidos pelo projeto; use prompts e descrições no
idioma desejado. Esta alteração não traduz automaticamente conteúdos de RAG,
respostas dos servidores MCP, mensagens declaradas por um workflow, arquivos de
exemplo, regras de negócio ou textos personalizados do aplicativo/frontend.
Prompts internos de classificação podem continuar em português: as decisões são
estruturadas. As instruções de recuperação que impunham português foram separadas
nos catálogos, incluindo sua variante em inglês.
## Personalizar sem alterar a biblioteca
Copie os catálogos para um diretório da aplicação e configure:
```dotenv
AGENT_LOCALE=en
AGENT_LOCALES_DIR=./config/locales
```
Nesse exemplo, o arquivo usado será `./config/locales/en.json`. O catálogo é
completo, e não um arquivo de alterações parciais. Preserve as chaves e os campos
como `{action}`, `{target}`, `{options}` e `{subject}` ao traduzir as frases.
Não é necessário manter a mesma quantidade de termos nas listas.
Reinicie o backend após editar os arquivos: o carregador mantém os catálogos em
cache. Na inicialização, ele verifica a estrutura, os campos das mensagens, a
sintaxe dos padrões e conflitos entre confirmação e recusa.
As principais seções são:
| Seção | Conteúdo |
|---|---|
| `terms.confirmation` | Confirmações e recusas explícitas de transações |
| `terms.workflow` | Respostas de compatibilidade para checkpoints antigos |
| `terms.multi_intent` | Conjunções, verbos e prefixos de confirmação |
| `terms.global_router` | Termos de continuidade e encerramento |
| `terms.groundedness` | Indícios de especificidade e incerteza |
| `terms.mock` | Termos dos classificadores locais usados em testes/desenvolvimento |
| `messages` | Frases de confirmação, nova tentativa, esclarecimento e fallback |
| `prompts.fallback` | Instruções de reescrita e regeneração por guardrail |
| `prompts.judge_rewrite` | Instruções de recuperação dos judges |
| `memory` | Padrões para extração de memória de longo prazo |
| `protocol` | Dígitos, letras e nomes usados na vocalização de protocolos |
| `safety` | Padrões determinísticos de segurança e escopo |
| `legacy_extraction` | Padrões do extrator legado de pedido/motivo |
## Decisões internas e contratos de workflow
Os códigos internos permanecem estáveis: `confirm`, `reject`, `SIM`, `NAO`,
`OUTRO` e nomes de estados não devem ser traduzidos em arquivos existentes.
O catálogo trata as palavras reconhecidas e os textos apresentados.
Um workflow que declara `expected_input`, `reprompt` ou mensagens próprias
continua tendo precedência. Para aceitar `YES` e `NO` em um contrato enumerado,
configure essas opções no workflow e ajuste suas transições, ou mantenha os
códigos atuais e use seu classificador semântico para mapear as respostas.
O dicionário global não substitui silenciosamente o contrato de um fluxo.
As listas de confirmação transacional usam correspondência da frase completa,
ignorando maiúsculas, espaços repetidos e pontuação final simples. Não inclua
frases ambíguas como “entendi”/“understood” nessas listas: compreender uma
explicação não equivale a autorizar uma operação. O fallback semântico existente
continua disponível para respostas que não constam do catálogo.
Os padrões de segurança mantêm cobertura em português e inglês nos dois arquivos.
Nomes de concorrentes presentes em `safety` preservam a configuração original de
telecom; adapte essas regras quando mudar o domínio da aplicação.
## Vários idiomas no mesmo processo
Para aplicações que selecionam o idioma por agente ou atendimento, envolva a
execução completa (roteamento, runtime e guardrails) no contexto correspondente:
```python
from agent_framework.localization import use_locale
with use_locale("en"):
# Execute aqui o roteamento, o agente e seus guardrails.
result = await handle_turn(...)
```
`handle_turn` representa o ponto de entrada da sua aplicação; não é uma nova API
do framework. O contexto usa `ContextVar`, é isolado entre tarefas assíncronas e
restaura o idioma anterior ao sair. Não altere `os.environ` a cada atendimento.
O idioma da conversa não é detectado automaticamente.
## Validação desta entrega
- 41 testes novos de localização aprovados.
- 160 testes selecionados aprovados após a atualização de oito testes antigos.
- As oito expectativas antigas estavam desalinhadas com mudanças de contrato
anteriores à localização. Histórico, escopo e validação estão em
`ATUALIZACAO_TESTES.md`. Os 66 testes dos quatro arquivos atualizados também
passaram com a biblioteca original do ZIP.
- As verificações incluem execução/cancelamento real do runtime com ferramentas
simuladas, confirmação determinística no roteador sem LLM, múltiplas intenções,
retomada de workflows, memória, protocolos, segurança, concorrência e catálogo
personalizado.
- Não foram feitas chamadas a modelos remotos, OCI ou ferramentas reais de negócio.
Os testes novos estão em `tests/test_locale_catalogs.py`. Para executá-los em um
ambiente com as dependências do projeto e pytest/pytest-asyncio:
```bash
PYTHONPATH=libs/agent_framework/src pytest -q tests/test_locale_catalogs.py
```
O pacote de entrega conserva os arquivos do projeto e remove metadados do macOS,
cache de bytecode, builds antigos e diretórios de Git/IDE. As fontes corrigidas
ficam em `libs/agent_framework/src`. As variantes de demonstração em
`Tuning-Performance` mantêm suas configurações de domínio originais.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +0,0 @@
# Multi-item result preservation fix
## Objective
Preserve the terminal outcome of each item executed by the primary transactional tool. A failure in a later auxiliary/composite step must not erase successful item outcomes or turn an already-completed multi-item operation into a total failure.
## Framework-only change
No agent/template business logic is required for this fix. The runtime now recognizes an explicit structural contract on the requested tool: a `results` array with two or more entries carrying boolean `success` or `ok` fields.
For workflow-backed tools the authoritative source is preferentially:
`workflow.output[tool_name].results`
The framework derives one of:
- `SUCCESS`
- `PARTIAL_SUCCESS`
- `FAILED`
and publishes a generic `metadata.multi_item` summary with item counts.
When one or more primary items completed successfully but the outer wrapper is `ok=false` because a later auxiliary/post-processing step failed, the normalized result preserves the original failure as `original_ok=false` / `secondary_error` while exposing the requested operation as operationally successful (`ok=true`).
The LLM context also receives an explicit generic instruction that successful per-item terminal evidence cannot be overwritten by a secondary error and that mixed outcomes must be reported individually.
## Regression tests
Added `tests/test_multi_item_result_normalization.py` covering:
- 2 success + 1 failure -> `PARTIAL_SUCCESS`;
- all primary items successful + later secondary failure -> primary `SUCCESS` preserved;
- single-item behavior unchanged;
- unrelated nested tool results cannot override failure of the requested primary tool.

View File

@@ -1,17 +0,0 @@
# Observability severity patch
- Central `severity_policy.py` classifies explicit MCP failures, guardrail blocks, judge rejections, RAG failures and memory/cache errors.
- `Telemetry.event()` accepts `level` and `status_message` and preserves WARNING/ERROR events as individual observations in compact mode.
- `Telemetry.span()` handle supports `set_status(level, message)`; failed MCP results mark their span ERROR without raising exceptions.
- Long-term-memory read/write failures emit telemetry while preserving existing fallback behavior.
- Guardrail blocked and negative judge evaluation emit WARNING when these helper methods are invoked.
- Normal cache misses and empty RAG results are not automatically failures.
## Scope / limitations
- Existing call sites must invoke `GuardrailTelemetry.blocked` for guardrail blocking events; this patch does not rewrite every pipeline call site.
- Redis cache currently has no injected Telemetry instance, so its existing logger errors are not yet Langfuse observations. A separate dependency-injection change is required to cover that path safely.
- RAG database connection errors that are caught by external KBDB integrations without emitting a telemetry event cannot be classified automatically.
- No live Langfuse ingestion test was possible in this environment. Verify both compact and verbose modes against your server.
## Validation
`PYTHONPATH=libs/agent_framework/src python -m pytest -q tests/test_observability_severity_policy.py`

9385
README.md

File diff suppressed because it is too large Load Diff

11178
README_en.md Normal file

File diff suppressed because it is too large Load Diff

11900
README_pt.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
# Reporting security vulnerabilities
Oracle values the independent security research community and believes that
responsible disclosure of security vulnerabilities helps us ensure the security
and privacy of all our users.
Please do NOT raise a GitHub Issue to report a security vulnerability. If you
believe you have found a security vulnerability, please submit a report to
[secalert_us@oracle.com][1] preferably with a proof of concept. Please review
some additional information on [how to report security vulnerabilities to Oracle][2].
We encourage people who contact Oracle Security to use email encryption using
[our encryption key][3].
We ask that you do not use other channels or contact the project maintainers
directly.
Non-vulnerability related security issues including ideas for new or improved
security features are welcome on GitHub Issues.
## Security updates, alerts and bulletins
Security updates will be released on a regular cadence. Many of our projects
will typically release security fixes in conjunction with the
Oracle Critical Patch Update program. Additional
information, including past advisories, is available on our [security alerts][4]
page.
## Security-related information
We will provide security related information such as a threat model, considerations
for secure use, or any known security issues in our documentation. Please note
that labs and sample code are intended to demonstrate a concept and may not be
sufficiently hardened for production use.
[1]: mailto:secalert_us@oracle.com
[2]: https://www.oracle.com/corporate/security-practices/assurance/vulnerability/reporting.html
[3]: https://www.oracle.com/security-alerts/encryptionkey.html
[4]: https://www.oracle.com/security-alerts/

View File

@@ -37,27 +37,3 @@ AGENT_AUTH_BASIC_REALM=agent-contas
# AGENT_AUTH_PROXY_SUBJECT_HEADER=x-authenticated-subject # AGENT_AUTH_PROXY_SUBJECT_HEADER=x-authenticated-subject
# AGENT_AUTH_PROXY_SHARED_SECRET_HEADER=x-internal-auth # AGENT_AUTH_PROXY_SHARED_SECRET_HEADER=x-internal-auth
# AGENT_AUTH_PROXY_SHARED_SECRET_HASH=sha256:replace-hex # AGENT_AUTH_PROXY_SHARED_SECRET_HASH=sha256:replace-hex
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -369,12 +369,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -255,28 +247,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -295,10 +265,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -443,18 +409,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -531,7 +485,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -594,51 +547,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -705,13 +623,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -938,7 +849,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -15,17 +15,11 @@ profiles:
temperature: 0 temperature: 0
max_tokens: 80 max_tokens: 80
timeout_seconds: 5 timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -67,30 +67,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -363,12 +363,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -255,28 +247,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -295,10 +265,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -443,18 +409,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -531,7 +485,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -594,51 +547,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -705,13 +623,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -938,7 +849,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -15,17 +15,11 @@ profiles:
temperature: 0 temperature: 0
max_tokens: 80 max_tokens: 80
timeout_seconds: 5 timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -1,16 +1,11 @@
# External Guardrails / Judges # External Guardrails / Judges
Este cenário de tuning mede extensões de política do agente. Antes de interpretar os resultados, implemente e valide o contrato descrito no [guia completo](../../docs/EXTERNAL_GUARDRAILS_JUDGES.md). Este exemplo parte do `agent_template_backend` e demonstra a composição de componentes nativos com políticas pertencentes ao agente.
O cenário deve comparar, no mínimo, latência p50/p95/p99, taxa de allow/deny/sanitize, erros e timeouts, custo de judges com LLM e comportamento fail-open/fail-closed. Separe resultados por código/nome e estágio, sem registrar prompts, PII ou payloads financeiros. - `type: external` ativa import dinâmico somente para o componente declarado.
- Guardrail/judge síncrono roda em worker thread via `asyncio.to_thread`.
- Implementação `async` roda concorrente no event loop.
- O framework não importa `app.extensions.*` por padrão.
- Use códigos/names próprios do domínio; não sobrescreva semanticamente o genérico sem deixar a substituição explícita no YAML.
Cuidados de leitura: Veja também `agent_framework_oci/docs/EXTERNAL_GUARDRAILS_JUDGES.md` e `docs/EXTERNAL_GUARDRAILS_JUDGES.md` no Contas.
- `type: external` afeta apenas o componente declarado;
- implementação síncrona usa worker thread e implementação `async` usa o event loop;
- judges executam concorrentemente, então meça contenção do provedor e do pool;
- compare a mesma carga com extensão desligada para obter o overhead incremental;
- execute casos transacionais separadamente, pois `always_run_for_transactional` ignora a amostragem;
- no template corrente os pipelines são globais; caminhos por agente não implicam isolamento automático.
Antes do benchmark, rode `pytest -q tests/test_external_guardrails_judges_documentation.py` para provar que os exemplos do manual ainda carregam e executam.

View File

@@ -66,30 +66,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -358,12 +358,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -253,28 +245,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -293,10 +263,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -441,18 +407,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -529,7 +483,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -592,51 +545,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -703,13 +621,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -867,7 +778,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -9,18 +9,11 @@ profiles:
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 700 max_tokens: 700
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -67,30 +67,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -363,12 +363,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -255,28 +247,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -295,10 +265,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -443,18 +409,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -531,7 +485,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -594,51 +547,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -705,13 +623,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -938,7 +849,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -15,17 +15,11 @@ profiles:
temperature: 0 temperature: 0
max_tokens: 80 max_tokens: 80
timeout_seconds: 5 timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -1,196 +0,0 @@
# Implementação prática — MCP Multi-Item
Este documento mostra como adicionar uma operação multi-item sem criar lógica paralela no agente e sem alterar desnecessariamente contratos já existentes.
## 1. Modele uma única operação lógica
```text
quero cancelar Produto A, Produto B e Produto C
```
A operação lógica continua sendo uma só. Não crie tools/intents por item nem loops no Agent.
## 2. Escolha o contrato natural da operação
### Opção A — parâmetro escalar
Use quando a interface já recebe um `subject`, `id`, `query` ou expressão que pode representar uma ou várias entidades:
```yaml
tools:
cancelar_vas_avulso:
args_schema:
subject:
type: string
```
Entrada:
```text
subject = "Produto A, Produto B, Produto C"
```
### Opção B — coleção explícita
Use quando a API/tool é naturalmente batch:
```yaml
tools:
cancelar_produtos:
args_schema:
items:
type: array
```
**Não transforme uma tool escalar em array apenas para obter suporte multi-item.**
## 3. Use a policy existente
Exemplo escalar:
```yaml
tool_policies:
cancelar_vas_avulso:
operation_type: transactional
require_confirmation: true
requires:
- subject
pre_validation:
enabled: true
tool: validar_vas_subject
fail_open: false
```
Nenhuma chave específica de multi-item é necessária.
## 4. Canonicalize/expanda no validator quando necessário
Entrada:
```json
{"subject": "produto a, produto b, produto c"}
```
Saída possível:
```json
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"subject": "Produto A, Produto B, Produto C",
"items": [
{"name": "Produto A", "id": "1001"},
{"name": "Produto B", "id": "1002"},
{"name": "Produto C", "id": "1003"}
]
},
"confirmation_message": "Você confirma o cancelamento de Produto A, Produto B e Produto C?"
}
}
```
O validator deve ser side-effect-free.
## 5. Não transforme uma falha individual em rejeição global
Caso:
```text
A = válido
B = inválido
C = válido
```
Com `fail_open: false`, isto bloquearia tudo:
```json
{"eligible": false, "reason": "B inválido"}
```
Quando A e C ainda podem ser executados, mantenha a transação globalmente elegível e preserve o estado individual dos itens.
## 6. Execute no MCP Server ou workflow
A tool/workflow pode receber `subject` e expandir internamente, ou receber `items[]` já canonicalizado.
### Backend com API unitária
```python
async def cancelar_produtos(items):
results = []
for item in items:
try:
response = await backend.cancel_one(item)
results.append({"name": item["name"], "success": True, "response": response})
except Exception as exc:
results.append({"name": item["name"], "success": False, "error": str(exc)})
return {"results": results}
```
O loop pertence ao **MCP Server/workflow**, não ao Agent.
## 7. Retorne `results[]` por item
```json
{
"results": [
{"name": "Produto A", "success": true},
{"name": "Produto B", "success": false, "error": "not_found"},
{"name": "Produto C", "success": true}
]
}
```
O framework aceita `success` ou `ok` booleano por item.
## 8. Resultado interpretado pelo framework
```text
items_count = 3
items_succeeded_count = 2
items_failed_count = 1
multi_item_status = PARTIAL_SUCCESS
```
## 9. Workflow com etapa auxiliar
O resultado autoritativo da operação principal deve estar em:
```text
workflow.output[tool_name].results
```
Assim uma falha auxiliar posterior não apaga itens já concluídos.
## 10. Checklist do desenvolvedor
- [ ] existe uma única tool lógica para a operação;
- [ ] `tools.yaml` declara o contrato natural, escalar ou coleção;
- [ ] não existe requisito de `type: array` para habilitar multi-item;
- [ ] `tool_policies.yaml` mantém os `requires` reais do domínio;
- [ ] se um escalar representa vários itens, validator/tool/workflow faz a expansão;
- [ ] com `fail_open: false`, falha individual não bloqueia automaticamente todos os itens acionáveis;
- [ ] fan-out, se necessário, pertence ao MCP Server/workflow;
- [ ] a tool primária devolve `results[]`;
- [ ] cada item contém `success` ou `ok` booleano;
- [ ] sucessos parciais não são apagados por uma falha global posterior;
- [ ] a apresentação comunica sucesso/falha por item;
- [ ] não foi criado um segundo motor multi-item no Agent.
## 11. Testes mínimos recomendados
```text
1 item / sucesso
N itens / todos sucesso
N itens / sucesso parcial
N itens / todos falha
entrada escalar expandida para N itens
entrada batch explícita
item não canonicalizado
confirmação única para N itens
falha auxiliar após sucesso primário
replay/idempotência da mesma operação
```
Para detalhes do contrato interno do runtime, consulte `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md`.

View File

@@ -1,205 +0,0 @@
# Multi-Item MCP
Capability de referência para operações MCP que precisam processar **mais de um item do mesmo tipo** em uma única intenção: produtos, pedidos, VAS, ativos, faturas, linhas, contratos ou recursos.
> Esta capability é **framework-native**. Ela não exige um motor multi-item dentro do agente e não adiciona novos atributos obrigatórios a `tools.yaml` ou `tool_policies.yaml`.
> **Importante:** multi-item não é definido por `type: array`. O parâmetro de entrada pode ser escalar (`subject`, `id`, `query`) ou uma coleção. O que torna a execução multi-item é a resolução/execução de vários itens e o retorno por item em `results[]`.
## Quando usar
Use quando o usuário puder solicitar, por exemplo:
```text
cancele Produto A, Produto B e Produto C
```
ou:
```text
consulte os pedidos 1001, 1002 e 1003
```
O desenvolvedor deve modelar **uma operação lógica**. O framework cuida de estado, confirmação e interpretação de resultados; validator/MCP Server/workflow cuidam da resolução e execução dos itens.
## Divisão de responsabilidades
```text
Agent
seleciona a operação
NÃO cria motor multi-item
↓
tools.yaml
declara o contrato natural da operação
pode ser escalar OU coleção
NÃO exige chave multi_item
↓
tool_policies.yaml
confirmação + requires + pre-validation
NÃO possui sintaxe especial de multi-item
↓
MCP validator
resolve/canonicaliza itens
pode expandir subject/id/query em items[]
pode preencher transaction_decision.resolved_arguments
↓
MCP tool / workflow
executa 1 ou N itens
devolve results[] com success/ok por item
↓
Agent Framework
mantém estado
controla confirmação
interpreta results[]
preserva evidência por item
calcula SUCCESS / PARTIAL_SUCCESS / FAILED
↓
LLM / Presentation
apresenta cada sucesso/falha
```
## Configuração mínima — padrão escalar
Este padrão é útil quando a interface natural já usa `subject` e o validator expande múltiplas entidades.
### `tools.yaml`
```yaml
tools:
cancelar_vas_avulso:
description: Cancela um ou mais VAS.
mcp_server: contas
enabled: true
tool_type: action
args_schema:
subject:
type: string
```
### `tool_policies.yaml`
```yaml
tool_policies:
cancelar_vas_avulso:
operation_type: transactional
require_confirmation: true
requires:
- subject
pre_validation:
enabled: true
tool: validar_vas_subject
fail_open: false
```
Nenhuma chave abaixo existe ou é necessária:
```yaml
multi_item: true
multi_item_strategy: partial
fan_out: framework
```
## Configuração alternativa — API batch
Quando a tool MCP recebe naturalmente uma coleção, `array/list` continua válido:
```yaml
args_schema:
items:
type: array
```
Isso é **uma opção de contrato**, não um pré-requisito para multi-item.
## Pre-validation e expansão de itens
Um `subject` escalar pode ser transformado em argumentos canônicos:
```json
{
"eligible": true,
"transaction_decision": {
"resolved_arguments": {
"subject": "Produto A, Produto B, Produto C",
"items": [
{"name": "Produto A", "id": "1001"},
{"name": "Produto B", "id": "1002"},
{"name": "Produto C", "id": "1003"}
]
},
"confirmation_message": "Você confirma o cancelamento de Produto A, Produto B e Produto C?"
}
}
```
### Validação parcial
`eligible` é global. Com `fail_open: false`, não retorne `eligible: false` apenas porque um item falhou se outros ainda são acionáveis.
Padrão recomendado:
1. `eligible: true` enquanto houver pelo menos um item acionável;
2. preservar resolução/eligibilidade individual;
3. executar a operação primária;
4. produzir o resultado terminal individual em `results[]`.
## Resultado MCP esperado
```json
{
"results": [
{"name": "Produto A", "success": true},
{"name": "Produto B", "success": false, "error": "not_found"},
{"name": "Produto C", "success": true}
]
}
```
O framework deriva automaticamente `SUCCESS`, `PARTIAL_SUCCESS` ou `FAILED`.
## Uma operação lógica, sem N chamadas no agente
São válidos:
```text
Agent -> cancelar(subject="A, B, C") -> validator/workflow -> results[]
```
ou:
```text
Agent -> cancelar(items=[A,B,C]) -> MCP/workflow -> results[]
```
Se o backend possui apenas API unitária, o fan-out fica no MCP Server/workflow. Evite loop MCP de negócio dentro do agente.
## Workflow-backed tools
Quando a operação é implementada por workflow, o caminho preferencial do resultado primário é:
```text
workflow.output[tool_name].results
```
Uma falha posterior não deve apagar sucesso terminal de itens já executados.
## Documentação completa
- `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE.md` — guia canônico em português;
- `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md` — guia canônico em inglês;
- `templates/agent_template_backend/docs/MCP_MULTI_ITEM.md` — referência junto ao template;
- `specs/SPEC-010-Agent-Development.md` — regra arquitetural.
Para o passo a passo prático, leia `IMPLEMENTACAO_MULTI_ITEM_MCP.md`.

View File

@@ -1,58 +0,0 @@
# Multi-Item MCP
Reference capability for MCP operations that process more than one item under a single logical user intent.
> Multi-item support is **not enabled by `type: array`**. The input may be scalar (`subject`, `id`, `query`) or a collection. Multi-item behavior comes from resolving/executing several items and returning per-item `results[]`.
```text
Agent selects the operation; no business fan-out
-> tools.yaml declares the natural input contract: scalar OR collection
-> tool_policies confirmation/requires/pre-validation; no special multi-item syntax
-> validator resolves/canonicalizes and may expand one value into items[]
-> MCP/workflow executes 1 or N items and returns results[]
-> framework preserves per-item evidence and derives SUCCESS/PARTIAL_SUCCESS/FAILED
-> presentation reports each success/failure
```
## Scalar pattern
```yaml
tool_policies:
cancelar_vas_avulso:
operation_type: transactional
require_confirmation: true
requires:
- subject
pre_validation:
enabled: true
tool: validar_vas_subject
fail_open: false
```
`subject` may represent one or many entities. The validator/workflow may expand it into canonical `items[]`.
## Explicit batch pattern
```yaml
args_schema:
items:
type: array
```
This is supported, but it is **an interface option, not a requirement**.
## Result contract
```json
{
"results": [
{"name": "A", "success": true},
{"name": "B", "success": false},
{"name": "C", "success": true}
]
}
```
The runtime derives `PARTIAL_SUCCESS` and preserves the successful items.
For full details, read `docs/MCP_MULTI_ITEM_DEVELOPER_GUIDE_en.md`.

View File

@@ -66,30 +66,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -358,12 +358,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -253,28 +245,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -293,10 +263,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -441,18 +407,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -529,7 +483,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -592,51 +545,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -703,13 +621,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -867,7 +778,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP. A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`. Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.

View File

@@ -9,18 +9,11 @@ profiles:
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 700 max_tokens: 700
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -66,30 +66,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -358,12 +358,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -253,28 +245,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -293,10 +263,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -441,18 +407,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -529,7 +483,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -592,51 +545,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -703,13 +621,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -867,7 +778,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -4,7 +4,7 @@ Este template suporta confirmação transacional em duas camadas: primeiro um pa
A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP. A configuração `router.transaction_confirmation.semantic_fallback` usa três classes: `SIM`, `NAO` e `CONTINUAR`. O prompt pode usar `{{ pending_prompt }}`, `{{ relevant_conversation_context }}`, `{{ user_input }}` e `{{ allowed_values }}`. O histórico injetado é apenas contexto de interpretação; não substitui validação de negócio ou evidência MCP.
Exemplo: após `Você confirma o cancelamento do serviço adicional selecionado?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação. Exemplo: após `Você confirma o cancelamento do serviço Tamboro Mensal?`, a frase `isso mesmo, pode confirmar` pode ser classificada como `SIM`. Já `mas qual é o valor?` deve ser `CONTINUAR`, portanto não executa a ação por confirmação.
Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`. Entradas explícitas já suportadas continuam no caminho determinístico e não geram custo adicional de LLM. Em observabilidade, o fallback usa `transaction.confirmation.semantic_classifier` e o `route_decision.metadata` informa `transaction_confirmation_source: semantic`.

View File

@@ -15,17 +15,11 @@ profiles:
temperature: 0 temperature: 0
max_tokens: 80 max_tokens: 80
timeout_seconds: 5 timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -8,4 +8,3 @@ Variantes e documentos de referência para comparar funcionalidades e impacto de
- `Deterministic_Transactional_Workflow`: transações multi-etapas executadas por workflow LangGraph determinístico após clarification e confirmação. - `Deterministic_Transactional_Workflow`: transações multi-etapas executadas por workflow LangGraph determinístico após clarification e confirmação.
- `Transaction_Evidence`: persistência e correlação de resultados transacionais como evidência operacional para turnos posteriores e groundedness. - `Transaction_Evidence`: persistência e correlação de resultados transacionais como evidência operacional para turnos posteriores e groundedness.
- `Transaction_Pre_Validation`: pré-validação MCP side-effect-free antes da confirmação transacional; regras de elegibilidade permanecem no domínio. - `Transaction_Pre_Validation`: pré-validação MCP side-effect-free antes da confirmação transacional; regras de elegibilidade permanecem no domínio.
- `Multi_Item_MCP`: operações MCP sobre listas de produtos/pedidos/serviços/ativos com confirmação única, canonicalização e resultado por item (`SUCCESS` / `PARTIAL_SUCCESS` / `FAILED`), sem criar motor multi-item no agente.

View File

@@ -66,30 +66,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -358,12 +358,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -253,28 +245,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -293,10 +263,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -441,18 +407,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -529,7 +483,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -592,51 +545,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -703,13 +621,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -867,7 +778,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -15,17 +15,11 @@ profiles:
temperature: 0 temperature: 0
max_tokens: 80 max_tokens: 80
timeout_seconds: 5 timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -66,30 +66,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -358,12 +358,9 @@ async def _process_gateway_message(req: GatewayRequest, emit_sse: bool = False)
"route": result.get("route"), "route": result.get("route"),
"intent": result.get("intent"), "intent": result.get("intent"),
"route_decision": result.get("route_decision"), "route_decision": result.get("route_decision"),
"supervisor_results": result.get("supervisor_results") or [],
"agent_responses": result.get("agent_responses") or result.get("supervisor_results") or [],
"domain": result.get("domain"), "domain": result.get("domain"),
"mcp_tools": result.get("mcp_tools"), "mcp_tools": result.get("mcp_tools"),
"mcp_results": result.get("mcp_results"), "mcp_results": result.get("mcp_results"),
"rag_results": result.get("rag_results"),
"business_context": business_context.model_dump(), "business_context": business_context.model_dump(),
"identity_missing": missing_identity_keys, "identity_missing": missing_identity_keys,
"judges": result.get("judge_results"), "judges": result.get("judge_results"),

View File

@@ -23,7 +23,6 @@ class AgentState(TypedDict, total=False):
domain: str domain: str
mcp_tools: list[str] mcp_tools: list[str]
mcp_results: list[dict[str, Any]] mcp_results: list[dict[str, Any]]
rag_results: list[dict[str, Any]]
available_mcp_tools: list[str] available_mcp_tools: list[str]
selected_tool_call: dict[str, Any] selected_tool_call: dict[str, Any]
pending_tool_call: dict[str, Any] pending_tool_call: dict[str, Any]
@@ -36,11 +35,6 @@ class AgentState(TypedDict, total=False):
missing_parameters: list[str] missing_parameters: list[str]
supervisor_plan: dict[str, Any] supervisor_plan: dict[str, Any]
supervisor_results: list[dict[str, Any]] supervisor_results: list[dict[str, Any]]
agent_responses: list[dict[str, Any]]
pending_topics: list[dict[str, Any]]
handled_topics: list[dict[str, Any]]
operation_results: dict[str, dict[str, Any]]
multi_intent_plan: dict[str, Any]
active_agent: str active_agent: str
route_bypassed: bool route_bypassed: bool
continuity_signal: dict[str, Any] continuity_signal: dict[str, Any]

View File

@@ -7,7 +7,6 @@ from agent_framework.guardrails.rail_action import RailAction
from agent_framework.guardrails.rail_result import RailResult from agent_framework.guardrails.rail_result import RailResult
from agent_framework.judges.judge import JudgePipeline from agent_framework.judges.judge import JudgePipeline
from agent_framework.routing.enterprise_router import EnterpriseRouter from agent_framework.routing.enterprise_router import EnterpriseRouter
from agent_framework.routing.pending_topics import drain_pending_topics
from agent_framework.supervisor.supervisor import Supervisor from agent_framework.supervisor.supervisor import Supervisor
from agent_framework.observability.workflow_events import WorkflowTelemetry from agent_framework.observability.workflow_events import WorkflowTelemetry
from agent_framework.observability.guardrail_events import GuardrailTelemetry from agent_framework.observability.guardrail_events import GuardrailTelemetry
@@ -110,6 +109,7 @@ class AgentWorkflow:
fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)), fail_fast=bool(getattr(settings, "GUARDRAILS_FAIL_FAST", True)),
) )
self.judges = JudgePipeline() self.judges = JudgePipeline()
self.supervisor = Supervisor()
self.workflow_telemetry = WorkflowTelemetry(telemetry) self.workflow_telemetry = WorkflowTelemetry(telemetry)
self.guardrail_telemetry = GuardrailTelemetry(telemetry) self.guardrail_telemetry = GuardrailTelemetry(telemetry)
self.judge_telemetry = JudgeTelemetry(telemetry) self.judge_telemetry = JudgeTelemetry(telemetry)
@@ -118,14 +118,6 @@ class AgentWorkflow:
self.embedding_provider = create_embedding_provider(settings) self.embedding_provider = create_embedding_provider(settings)
self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry) self.rag_service = RagService(settings, embedding_provider=self.embedding_provider, telemetry=telemetry)
self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry) self.router = EnterpriseRouter(settings, llm=llm, telemetry=telemetry)
self.supervisor = Supervisor(
routing_rules=[
(intent.name, intent.agent, list(intent.keywords))
for intent in self.router.intents
if intent.enabled
],
fallback_agent=self.router.fallback_agent,
)
agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory} agent_kwargs = {"telemetry": telemetry, "tool_router": getattr(self, "tool_router", None), "rag_service": self.rag_service, "cache": self.cache, "settings": settings, "observer": self.observer, "memory": memory, "summary_memory": summary_memory}
self.billing = BillingAgent(llm, **agent_kwargs) self.billing = BillingAgent(llm, **agent_kwargs)
self.product = ProductAgent(llm, **agent_kwargs) self.product = ProductAgent(llm, **agent_kwargs)
@@ -253,28 +245,6 @@ class AgentWorkflow:
input=state.get("user_text"), input=state.get("user_text"),
): ):
history_texts = [m.get("content", "") for m in state.get("history", [])] history_texts = [m.get("content", "") for m in state.get("history", [])]
guardrail_active_transaction = (
state.get("active_transaction")
if isinstance(state.get("active_transaction"), dict)
else {}
)
guardrail_transaction_status = str(
guardrail_active_transaction.get("status")
or state.get("transaction_status")
or ""
).strip().upper()
pending_call = state.get("pending_tool_call") if isinstance(state.get("pending_tool_call"), dict) else {}
if state.get("confirmation_required") and pending_call.get("tool_name"):
guardrail_transaction_status = "AWAITING_CONFIRMATION"
elif state.get("missing_parameters") and pending_call.get("tool_name"):
guardrail_transaction_status = "COLLECTING_PARAMETERS"
for mcp_result in reversed(list(state.get("mcp_results") or [])):
if not isinstance(mcp_result, dict):
continue
mcp_status = str(mcp_result.get("transaction_status") or "").strip().upper()
if mcp_status in {"COLLECTING_PARAMETERS", "AWAITING_CONFIRMATION"}:
guardrail_transaction_status = mcp_status
break
await self.observer.emit_grl( await self.observer.emit_grl(
"001", "001",
{ {
@@ -293,10 +263,6 @@ class AgentWorkflow:
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"agent_profile": state.get("agent_profile") or {}, "agent_profile": state.get("agent_profile") or {},
"transaction_status": guardrail_transaction_status,
"missing_parameters": list(state.get("missing_parameters") or []),
"active_transaction": guardrail_active_transaction,
"mcp_results": list(state.get("mcp_results") or []),
}, },
) )
for _decision in decisions: for _decision in decisions:
@@ -441,18 +407,6 @@ class AgentWorkflow:
"reason": decision.reason if decision.method == "continuity" else None, "reason": decision.reason if decision.method == "continuity" else None,
"profile": (decision.metadata or {}).get("continuity_profile"), "profile": (decision.metadata or {}).get("continuity_profile"),
} if decision.method == "continuity" else {}, } if decision.method == "continuity" else {},
"pending_topics": (
(decision.metadata or {}).get("multi_intent_plan", {}).get("operations", [])[1:]
if (decision.metadata or {}).get("multi_intent_plan")
else list(state.get("pending_topics") or [])
),
# Durable correlation for the primary operation. route_decision
# is replaced by state routing on confirmation/resume turns.
"multi_intent_plan": (
(decision.metadata or {}).get("multi_intent_plan")
or state.get("multi_intent_plan")
or {}
),
} }
async def billing_agent(self, state): async def billing_agent(self, state):
@@ -529,7 +483,6 @@ class AgentWorkflow:
return { return {
"answer": answer, "answer": answer,
"supervisor_results": partials, "supervisor_results": partials,
"agent_responses": partials,
"mcp_results": mcp_results, "mcp_results": mcp_results,
"next_state": "SUPERVISOR_ACTIVE", "next_state": "SUPERVISOR_ACTIVE",
} }
@@ -592,51 +545,16 @@ class AgentWorkflow:
agente gerar `answer` e antes dos judges/persistência, produzindo campos agente gerar `answer` e antes dos judges/persistência, produzindo campos
supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver. supervisor_* no state e eventos GRL.001..GRL.009 via AgentObserver.
""" """
drained = await drain_pending_topics(
state,
str(state.get("answer") or ""),
{
"billing_agent": self.billing.run,
"product_agent": self.product.run,
"orders_agent": self.orders.run,
"support_agent": self.support.run,
},
)
candidate = drained.answer
if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)): if not bool(getattr(self.settings, "ENABLE_OUTPUT_SUPERVISOR", True)):
return { return {
"answer": candidate,
"final_answer": candidate,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
"output_guardrails_already_applied": False, "output_guardrails_already_applied": False,
"supervisor_action": "disabled", "supervisor_action": "disabled",
"supervisor_attempt": int(state.get("supervisor_attempt", 0)), "supervisor_attempt": int(state.get("supervisor_attempt", 0)),
**drained.state_patch,
} }
candidate = state.get("answer") or ""
context = { context = {
**(state.get("context") or {}), **(state.get("context") or {}),
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"evidence": (list(drained.mcp_results) + list(drained.rag_results)) or (state.get("context") or {}).get("evidence"),
"tool_result": drained.mcp_results or (state.get("context") or {}).get("tool_result"),
"tool_executed": any(isinstance(item, dict) and item.get("ok") for item in drained.mcp_results),
"mcp_results": drained.mcp_results,
"tenant_id": state.get("tenant_id"), "tenant_id": state.get("tenant_id"),
"agent_id": state.get("agent_id"), "agent_id": state.get("agent_id"),
"session_id": state.get("conversation_key") or state.get("session_id"), "session_id": state.get("conversation_key") or state.get("session_id"),
@@ -703,13 +621,6 @@ class AgentWorkflow:
for r in decision.results for r in decision.results
], ],
"output_guardrails_already_applied": True, "output_guardrails_already_applied": True,
"pending_topics": drained.pending_topics,
"handled_topics": drained.handled_topics,
"agent_responses": drained.agent_responses,
"operation_results": drained.operation_results,
"mcp_results": drained.mcp_results,
"rag_results": drained.rag_results,
**drained.state_patch,
"guardrail_decisions": state.get("guardrail_decisions", []) "guardrail_decisions": state.get("guardrail_decisions", [])
+ [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])], + [item for r in decision.results for item in (r.metadata or {}).get("legacy_decisions", [])],
} }
@@ -867,7 +778,6 @@ class AgentWorkflow:
"judges": state.get("judge_results", []), "judges": state.get("judge_results", []),
"mcp_tools": state.get("mcp_tools", []), "mcp_tools": state.get("mcp_tools", []),
"mcp_results": state.get("mcp_results", []), "mcp_results": state.get("mcp_results", []),
"rag_results": state.get("rag_results", []),
}, },
) )

View File

@@ -15,17 +15,11 @@ profiles:
temperature: 0 temperature: 0
max_tokens: 80 max_tokens: 80
timeout_seconds: 5 timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router: router:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1
temperature: 0 temperature: 0
max_tokens: 768 max_tokens: 500
guardrail: guardrail:
provider: oci_openai provider: oci_openai
model: openai.gpt-4.1 model: openai.gpt-4.1

View File

@@ -66,30 +66,6 @@ EMBEDDING_PROVIDER=mock
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0 OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json RAG_FILE_GLOBS=*.md,*.txt,*.yaml,*.yml,*.json
# RAG / KBDB
# RAG provider: standard (local) ou kbdb (PKG_KB_SERVING.SEARCH_KNOWLEDGE_BASE)
RAG_PROVIDER=standard
# KBDB usa credenciais próprias; não há fallback para ADB_*.
KBDB_DB_USER=
KBDB_DB_PASSWORD=
KBDB_DB_DSN=
KBDB_DB_WALLET_LOCATION=
KBDB_DB_WALLET_PASSWORD=
KBDB_SEARCH_TYPE=hybrid
KBDB_NODE_EXPANSION=true
KBDB_NODE_MAX_RELATED=8
KBDB_GRAPH_CROSS_REF=false
KBDB_MAX_CROSS_REF_HOPS=1
KBDB_DOCUMENT_TYPE=customer_safe
KBDB_METADATA_JSON=
KBDB_MIN_SCORE=
KBDB_IDENTIFY_DOCUMENT=true
KBDB_STORE_QUERY=true
KBDB_IDENTIFY_TOP_N=3
RAG_GROUNDED_ONLY=false
KBDB_GROUNDED_ONLY=true
############################################################################### ###############################################################################
# Observabilidade # Observabilidade
############################################################################### ###############################################################################

View File

@@ -108,14 +108,6 @@ class BillingAgent(AgentRuntimeMixin):
"next_state": "BILLING_ACTIVE", "next_state": "BILLING_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class OrdersAgent(AgentRuntimeMixin):
"next_state": "ORDER_ACTIVE", "next_state": "ORDER_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class ProductAgent(AgentRuntimeMixin):
"next_state": "PRODUCT_ACTIVE", "next_state": "PRODUCT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

View File

@@ -108,14 +108,6 @@ class SupportAgent(AgentRuntimeMixin):
"next_state": "SUPPORT_ACTIVE", "next_state": "SUPPORT_ACTIVE",
"mcp_results": tool_context, "mcp_results": tool_context,
"rag": rag_metadata, "rag": rag_metadata,
"rag_context": rag_context,
"rag_results": [{
"intent": state.get("intent"),
"agent": self.name,
"source_text": str(state.get("sanitized_input") or state.get("user_text") or ""),
"metadata": rag_metadata,
**({"context": rag_context} if rag_context else {}),
}],
"memory_context_metadata": state.get("memory_context_metadata"), "memory_context_metadata": state.get("memory_context_metadata"),
**self.transaction_state_patch(state), **self.transaction_state_patch(state),
} }

Some files were not shown because too many files have changed in this diff Show More