Compare commits

..

62 Commits

Author SHA1 Message Date
a30656210e Adjustments for /oracle organization 2026-10-21 08:38:42 -03:00
3765c3f43a A2A Protocol 2026-10-21 08:23:55 -03:00
T3782834
b08affa48c bugfix: rollback of handoff 2026-09-22 23:42:40 -03:00
T3782834
05b34a696e bugfix: handoff 2026-09-22 21:36:07 -03:00
T3782834
f33909e258 bugfix: handoff 2026-09-22 21:27:37 -03:00
01b44773ec bugfix: max_tokens hardcoded 2026-09-19 00:30:51 -03:00
f942610bc5 Multi-item documentation 2026-09-18 23:35:03 -03:00
c582150e1c bugfix: Multi-item 2026-09-18 23:11:58 -03:00
36f97811fa new feature: transaction interruption for same intent replacement 2026-09-18 11:47:29 -03:00
d606744d2e bugfix: ABANDON precedence 2026-09-17 10:23:35 -03:00
0d2b0f1084 adding ABANDON in multi-intent/intent-shift 2026-09-17 00:06:00 -03:00
0d0c95f9f8 adjustment: extra attributes for OTEL 2026-09-16 20:36:55 -03:00
583d801d4e manual agent_template_backend 2026-09-16 08:57:20 -03:00
4712bcc0dc adjustments: manual v4 2026-09-13 08:52:57 -03:00
e16d19083d adjustments: manual v3 2026-09-12 15:33:55 -03:00
dd207b4078 adjustments: manual v2 2026-09-12 08:04:25 -03:00
2f9b053098 adjustments: manual 2026-09-12 07:11:07 -03:00
7a22b7af11 new feature: frontend with separated responses 2026-09-11 11:59:46 -03:00
e882afde25 new feature: frontend with separated responses 2026-09-11 10:45:49 -03:00
f00c2f721a new feature: frontend with separated responses 2026-09-11 09:33:50 -03:00
f62c7993bb new feature: Multi-Intent 2026-09-11 00:34:56 -03:00
3f884d028a new feature: Multi-Intent 2026-09-10 22:55:50 -03:00
05410adf09 new feature: Multi-Intent 2026-09-10 16:12:04 -03:00
ffe47e8d49 ajustes no manual 2026-09-10 09:48:25 -03:00
ae5887ce50 adjustment: load-tester 2026-09-09 13:51:05 -03:00
82636ec15b bugfix: extractor cases 8 and 16 2026-09-08 10:13:03 -03:00
1711575abe bugfix reconciliation extractor 2026-09-07 22:57:36 -03:00
2c3aa58b59 bugfix reconciliation extractor 2026-09-07 20:16:57 -03:00
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
387 changed files with 1504 additions and 19041 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

67
.idea/workspace.xml generated
View File

@@ -4,9 +4,7 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="30a0e1d8-9d7d-469b-b241-f300911cee8a" name="Changes" comment="Ajustes na documentação e remanejamento dos folders"> <list default="true" id="30a0e1d8-9d7d-469b-b241-f300911cee8a" name="Changes" comment="Ajustes na documentação e remanejamento dos folders" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -47,35 +45,34 @@
<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": "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;, "Shell Script.agent_frontend.executor": "Run",
&quot;Shell Script.agent_frontend.executor&quot;: &quot;Run&quot;, "Shell Script.agent_template_backend.executor": "Run",
&quot;Shell Script.agent_template_backend.executor&quot;: &quot;Run&quot;, "Shell Script.mcp_gateway.executor": "Run",
&quot;Shell Script.mcp_gateway.executor&quot;: &quot;Run&quot;, "Shell Script.mcp_server.executor": "Run",
&quot;Shell Script.mcp_server.executor&quot;: &quot;Run&quot;, "git-widget-placeholder": "master",
&quot;git-widget-placeholder&quot;: &quot;main&quot;, "kotlin-language-version-configured": "true",
&quot;kotlin-language-version-configured&quot;: &quot;true&quot;, "last_opened_file_path": "C:/Asus_Projects/kbdb/agent_framework_oci/templates/agent_template_backend",
&quot;last_opened_file_path&quot;: &quot;D:/Dropbox/ORACLE/TIM/FY27/Commits/agent_platform_oci&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "nodejs_package_manager_path": "npm",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "project.structure.last.edited": "Project",
&quot;project.structure.last.edited&quot;: &quot;Project&quot;, "project.structure.proportion": "0.15",
&quot;project.structure.proportion&quot;: &quot;0.15&quot;, "project.structure.side.proportion": "0.2",
&quot;project.structure.side.proportion&quot;: &quot;0.2&quot;, "settings.editor.selected.configurable": "advanced.settings",
&quot;settings.editor.selected.configurable&quot;: &quot;advanced.settings&quot;, "vue.rearranger.settings.migration": "true"
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
} }
}</component> }]]></component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="C:\Asus_Projects\kbdb\agent_framework_oci\templates\agent_template_backend" /> <recent name="C:\Asus_Projects\kbdb\agent_framework_oci\templates\agent_template_backend" />
@@ -152,8 +149,8 @@
<component name="SharedIndexes"> <component name="SharedIndexes">
<attachedChunks> <attachedChunks>
<set> <set>
<option value="bundled-jdk-30f59d01ecdd-cffe25b9f5b3-intellij.indexing.shared.core-IU-253.28294.334" /> <option value="bundled-jdk-9823dce3aa75-fbdcb00ec9e3-intellij.indexing.shared.core-IU-251.29188.36" />
<option value="bundled-js-predefined-d6986cc7102b-c7e53b3be11b-JavaScript-IU-253.28294.334" /> <option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-IU-251.29188.36" />
</set> </set>
</attachedChunks> </attachedChunks>
</component> </component>
@@ -177,10 +174,6 @@
<workItem from="1785409583147" duration="1561000" /> <workItem from="1785409583147" duration="1561000" />
<workItem from="1785452072692" duration="784000" /> <workItem from="1785452072692" duration="784000" />
<workItem from="1785453511270" duration="1006000" /> <workItem from="1785453511270" duration="1006000" />
<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

@@ -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,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.

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`

View File

@@ -49,91 +49,89 @@ If something is not working or if you want to understand the features of the Age
[English technical specifications (SPEC-001–SPEC-020)](specs/en/README.md)
## SPECs / SDDs of the Agent Platform OCI ## SPECs / SDDs of the Agent Platform OCI
The Agent Platform OCI documentation is organized into numbered SPECs/SDDs, each covering an architectural, operational, or governance area of the platform. The objective is to standardize the construction, evolution, operation, and certification of enterprise agents based on the Agent Framework OCI. The Agent Platform OCI documentation is organized into numbered SPECs/SDDs, each covering an architectural, operational, or governance area of the platform. The objective is to standardize the construction, evolution, operation, and certification of enterprise agents based on the Agent Framework OCI.
>**/agent_platform_oci/specs** >**/agent_platform_oci/specs**
### [SPEC-001 — Architecture](specs/en/SPEC-001-Architecture.md) ### [SPEC-001 — Architecture](specs/SPEC-001-Architecture.md)
Defines the overall platform architecture, its main components, repository structure, logical and physical architecture, core contracts, primary flow, non-functional requirements, and acceptance criteria. Defines the overall platform architecture, its main components, repository structure, logical and physical architecture, core contracts, primary flow, non-functional requirements, and acceptance criteria.
### [SPEC-002 — Agent Runtime](specs/en/SPEC-002-Agent-Runtime.md) ### [SPEC-002 — Agent Runtime](specs/SPEC-002-Agent-Runtime.md)
Describes the conversational execution runtime for agents, including LangGraph, state management, memory, checkpoints, routing, supervisor, BusinessContext, MCP integration, RAG, events, and error handling. Describes the conversational execution runtime for agents, including LangGraph, state management, memory, checkpoints, routing, supervisor, BusinessContext, MCP integration, RAG, events, and error handling.
### [SPEC-003 — Agent Gateway](specs/en/SPEC-003-Agent-Gateway.md) ### [SPEC-003 — Agent Gateway](specs/SPEC-003-Agent-Gateway.md)
Specifies the gateway responsible for centralizing LLM and embedding calls, including request/response contracts, profiles, providers, OCI authentication, fallback, rate limiting, metrics, security, and observability. Specifies the gateway responsible for centralizing LLM and embedding calls, including request/response contracts, profiles, providers, OCI authentication, fallback, rate limiting, metrics, security, and observability.
### [SPEC-004 — MCP Gateway](specs/en/SPEC-004-MCP-Gateway.md) ### [SPEC-004 — MCP Gateway](specs/SPEC-004-MCP-Gateway.md)
Defines the MCP integration model, including the tool catalog, routing, execution, authorization, caching, retries, timeouts, parameter mapping, events, metrics, and standardized tool responses. Defines the MCP integration model, including the tool catalog, routing, execution, authorization, caching, retries, timeouts, parameter mapping, events, metrics, and standardized tool responses.
### [SPEC-005 — Guardrails](specs/en/SPEC-005-Guardrails.md) ### [SPEC-005 — Guardrails](specs/SPEC-005-Guardrails.md)
Describes the platform guardrail model, covering input, output, tool, RAG, and final response policies. It also defines phases, execution modes, guardrail types, LLM profiles, base codes, events, tests, and acceptance criteria. Describes the platform guardrail model, covering input, output, tool, RAG, and final response policies. It also defines phases, execution modes, guardrail types, LLM profiles, base codes, events, tests, and acceptance criteria.
### [SPEC-006 — Evals](specs/en/SPEC-006-Evals.md) ### [SPEC-006 — Evals](specs/SPEC-006-Evals.md)
Defines the platform evaluation layer, including online evaluation, offline evaluation, regression testing, certification, datasets, judges, metrics, CLI, API, result persistence, and evidence publication. Defines the platform evaluation layer, including online evaluation, offline evaluation, regression testing, certification, datasets, judges, metrics, CLI, API, result persistence, and evidence publication.
### [SPEC-007 — Observability](specs/en/SPEC-007-Observability.md) ### [SPEC-007 — Observability](specs/SPEC-007-Observability.md)
Specifies the observability model, including logs, traces, metrics, Langfuse, OpenTelemetry, IC/NOC/GRL events, dashboards, alerts, data masking, and operational evidence generation. Specifies the observability model, including logs, traces, metrics, Langfuse, OpenTelemetry, IC/NOC/GRL events, dashboards, alerts, data masking, and operational evidence generation.
### [SPEC-008 — Deployment](specs/en/SPEC-008-Deployment.md) ### [SPEC-008 — Deployment](specs/SPEC-008-Deployment.md)
Describes the platform packaging and deployment process, including deployable components, CI/CD pipelines, Kubernetes/OKE, Docker, secrets, OCI authentication, health checks, rollback, smoke tests, and certification stages. Describes the platform packaging and deployment process, including deployable components, CI/CD pipelines, Kubernetes/OKE, Docker, secrets, OCI authentication, health checks, rollback, smoke tests, and certification stages.
### [SPEC-009 — Channel Gateway](specs/en/SPEC-009-Channel-Gateway.md) ### [SPEC-009 — Channel Gateway](specs/SPEC-009-Channel-Gateway.md)
Defines the channel gateway responsible for normalizing external payloads into the platform's canonical contract and translating responses for each channel. Covers operating modes, idempotency, versioning, security, errors, and anti-patterns. Defines the channel gateway responsible for normalizing external payloads into the platform's canonical contract and translating responses for each channel. Covers operating modes, idempotency, versioning, security, errors, and anti-patterns.
### [SPEC-010 — Agent Development](specs/en/SPEC-010-Agent-Development.md) ### [SPEC-010 — Agent Development](specs/SPEC-010-Agent-Development.md)
Describes the standard for agent development using templates, YAML configuration, BusinessContext, MCP, guardrails, judges, RAG, memory, observability, and evaluations. It also differentiates framework and agent responsibilities. Describes the standard for agent development using templates, YAML configuration, BusinessContext, MCP, guardrails, judges, RAG, memory, observability, and evaluations. It also differentiates framework and agent responsibilities.
### [SPEC-011 — Governance Model](specs/en/SPEC-011-Governance-Model.md) ### [SPEC-011 — Governance Model](specs/SPEC-011-Governance-Model.md)
Defines the platform governance model, including ownership, roles and responsibilities, RACI, agent governance, prompts, guardrails, judges, models, MCP, datasets, approval processes, and mandatory evidence. Defines the platform governance model, including ownership, roles and responsibilities, RACI, agent governance, prompts, guardrails, judges, models, MCP, datasets, approval processes, and mandatory evidence.
### [SPEC-012 — Canonical Contracts](specs/en/SPEC-012-Canonical-Contracts.md) ### [SPEC-012 — Canonical Contracts](specs/SPEC-012-Canonical-Contracts.md)
Documents the platform's canonical contracts, including GatewayRequest, ChannelResponse, BusinessContext, AgentState, ToolInvocation, ToolResult, LLMRequest, LLMResponse, EvaluationRun, and EventEnvelope. It also defines contract evolution rules. Documents the platform's canonical contracts, including GatewayRequest, ChannelResponse, BusinessContext, AgentState, ToolInvocation, ToolResult, LLMRequest, LLMResponse, EvaluationRun, and EventEnvelope. It also defines contract evolution rules.
### [SPEC-013 — Versioning and Compatibility Model](specs/en/SPEC-013-Versioning-and-Compatibility-Model.md) ### [SPEC-013 — Versioning and Compatibility Model](specs/SPEC-013-Versioning-and-Compatibility-Model.md)
Defines the platform versioning and compatibility model, including Semantic Versioning, versioned artifacts, contract versioning, compatibility matrices, deprecation policies, migration, and rollback. Defines the platform versioning and compatibility model, including Semantic Versioning, versioned artifacts, contract versioning, compatibility matrices, deprecation policies, migration, and rollback.
### [SPEC-014 — Templates and Agent Creation Model](specs/en/SPEC-014-Templates-and-Agent-Creation-Model.md) ### [SPEC-014 — Templates and Agent Creation Model](specs/SPEC-014-Templates-and-Agent-Creation-Model.md)
Describes the official templates and the agent creation model from scratch. Explains what belongs to the framework, what belongs to the agent, the standard structure, and the step-by-step process for copying templates, defining scope, registering agents, configuring routes, tools, BusinessContext, prompts, datasets, and tests. Describes the official templates and the agent creation model from scratch. Explains what belongs to the framework, what belongs to the agent, the standard structure, and the step-by-step process for copying templates, defining scope, registering agents, configuring routes, tools, BusinessContext, prompts, datasets, and tests.
### [SPEC-015 — Adoption and Eligibility Criteria](specs/en/SPEC-015-Adoption-and-Eligibility-Criteria.md) ### [SPEC-015 — Adoption and Eligibility Criteria](specs/SPEC-015-Adoption-and-Eligibility-Criteria.md)
Defines clear platform adoption criteria, including recommended and non-recommended use cases, business entry criteria, architecture, security, quality, operations, exception processes, and adoption checklists. Defines clear platform adoption criteria, including recommended and non-recommended use cases, business entry criteria, architecture, security, quality, operations, exception processes, and adoption checklists.
### [SPEC-016 — Agent Development Lifecycle](specs/en/SPEC-016-Agent-Development-Lifecycle.md) ### [SPEC-016 — Agent Development Lifecycle](specs/SPEC-016-Agent-Development-Lifecycle.md)
Describes the complete agent development lifecycle, from discovery and scope definition through design, prompting, MCP, RAG, implementation, testing, evaluation, certification, validation, and production. Describes the complete agent development lifecycle, from discovery and scope definition through design, prompting, MCP, RAG, implementation, testing, evaluation, certification, validation, and production.
### [SPEC-017 — Release Management and CI/CD](specs/en/SPEC-017-Release-Management-and-CICD.md) ### [SPEC-017 — Release Management and CI/CD](specs/SPEC-017-Release-Management-and-CICD.md)
Defines the release and CI/CD model, including standard pipelines, stages, release artifacts, quality gates, rollback strategies, common errors, and acceptance criteria. Defines the release and CI/CD model, including standard pipelines, stages, release artifacts, quality gates, rollback strategies, common errors, and acceptance criteria.
### [SPEC-018 — Security and Identity Model](specs/en/SPEC-018-Security-and-Identity-Model.md) ### [SPEC-018 — Security and Identity Model](specs/SPEC-018-Security-and-Identity-Model.md)
Specifies the platform security and identity model, covering authentication, Workload Identity, authorization, secrets, data protection, MCP security, channel security, auditing, and acceptance criteria. Specifies the platform security and identity model, covering authentication, Workload Identity, authorization, secrets, data protection, MCP security, channel security, auditing, and acceptance criteria.
### [SPEC-019 — Evaluation and Certification Framework](specs/en/SPEC-019-Evaluation-and-Certification-Framework.md) ### [SPEC-019 — Evaluation and Certification Framework](specs/SPEC-019-Evaluation-and-Certification-Framework.md)
Details the evaluation and certification framework, including evaluation architecture, metrics, datasets, EvaluationRun, CLI, certification processes, mandatory evidence, and acceptance criteria. Details the evaluation and certification framework, including evaluation architecture, metrics, datasets, EvaluationRun, CLI, certification processes, mandatory evidence, and acceptance criteria.
### [SPEC-020 — Operational Readiness and SRE Model](specs/en/SPEC-020-Operational-Readiness-and-SRE-Model.md) ### [SPEC-020 — Operational Readiness and SRE Model](specs/SPEC-020-Operational-Readiness-and-SRE-Model.md)
Defines the platform operational readiness and SRE model, including managed components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity planning, and production checklists. Defines the platform operational readiness and SRE model, including managed components, health checks, readiness, SLOs, metrics, dashboards, alerts, runbooks, incident management, capacity planning, and production checklists.

View File

@@ -73,7 +73,7 @@ function renderResponse(response) {
for (const part of agentMessages(response)) { for (const part of agentMessages(response)) {
const section=document.createElement('section'); section.className='agent-section'; const section=document.createElement('section'); section.className='agent-section';
const header=document.createElement('div'); header.className='agent-header'; const header=document.createElement('div'); header.className='agent-header';
const avatar=document.createElement('span'); avatar.className='agent-avatar'; avatar.textContent='O'; avatar.setAttribute('aria-hidden','true'); const avatar=document.createElement('span'); avatar.className='agent-avatar'; avatar.textContent='✳'; avatar.setAttribute('aria-hidden','true');
const name=document.createElement('span'); name.textContent=String(part.agent).replaceAll('_',' '); const name=document.createElement('span'); name.textContent=String(part.agent).replaceAll('_',' ');
const body=document.createElement('div'); body.className='agent-body'; body.textContent=typeof part.text==='string' ? part.text : JSON.stringify(part.text,null,2); const body=document.createElement('div'); body.className='agent-body'; body.textContent=typeof part.text==='string' ? part.text : JSON.stringify(part.text,null,2);
header.append(avatar,name); section.append(header,body); group.append(section); header.append(avatar,name); section.append(header,body); group.append(section);

View File

@@ -2,12 +2,12 @@
<html lang="pt-BR"> <html lang="pt-BR">
<head> <head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Oracle Agent Workspace · A2A</title><link rel="stylesheet" href="styles.css"> <title>Agent Workspace · A2A</title><link rel="stylesheet" href="styles.css">
</head> </head>
<body> <body>
<div class="workspace"> <div class="workspace">
<aside class="sidebar"> <aside class="sidebar">
<a class="brand" href="./"><img class="oracle-logo" src="oracle-logo.svg" alt="Oracle"><span class="brand-copy">Agent Workspace<small>FRAMEWORK OCI · A2A</small></span></a> <a class="brand" href="./"><span class="brand-mark" aria-hidden="true">a</span><span>Agent Workspace<small>FRAMEWORK OCI · A2A</small></span></a>
<div class="sidebar-heading"><span>CONEXÃO</span><span class="badge" id="versionBadge">A2A</span></div> <div class="sidebar-heading"><span>CONEXÃO</span><span class="badge" id="versionBadge">A2A</span></div>
<form id="connectionForm" class="connection-form"> <form id="connectionForm" class="connection-form">
<label>Endereço do serviço<input id="serviceUrl" type="url" value="http://localhost:8000" required spellcheck="false"></label> <label>Endereço do serviço<input id="serviceUrl" type="url" value="http://localhost:8000" required spellcheck="false"></label>
@@ -41,7 +41,7 @@
</section> </section>
<div id="notice" class="notice" role="alert" hidden></div> <div id="notice" class="notice" role="alert" hidden></div>
<section id="chat" class="chat" role="log" aria-label="Conversa com o agente" aria-live="polite"> <section id="chat" class="chat" role="log" aria-label="Conversa com o agente" aria-live="polite">
<div id="welcome" class="welcome"><span class="eyebrow">CONVERSAS QUE VIRAM AÇÕES</span><h2>O próximo passo<br>começa com uma mensagem.</h2><p>Conecte um agente ou o Gateway do framework.<br>Acompanhe cada tarefa até a resposta.</p><div class="suggestions"><button type="button" data-prompt="O que você pode fazer por mim?">Conhecer o agente <span>↗</span></button><button type="button" data-prompt="Quero consultar meu pedido.">Consultar um pedido <span>↗</span></button></div></div> <div id="welcome" class="welcome"><span class="welcome-icon" aria-hidden="true">✳</span><span class="eyebrow">CONVERSAS QUE VIRAM AÇÕES</span><h2>O próximo passo<br>começa com uma mensagem.</h2><p>Conecte um agente ou o Gateway do framework.<br>Acompanhe cada tarefa até a resposta.</p><div class="suggestions"><button type="button" data-prompt="O que você pode fazer por mim?">Conhecer o agente <span>↗</span></button><button type="button" data-prompt="Quero consultar meu pedido.">Consultar um pedido <span>↗</span></button></div></div>
</section> </section>
<div class="composer-area"><form id="messageForm" class="composer"><label class="sr-only" for="message">Mensagem para o agente</label><textarea id="message" rows="1" placeholder="Escreva uma mensagem para começar…" disabled></textarea><button id="sendButton" type="submit" class="send-button" aria-label="Enviar mensagem" disabled>↑</button></form><div class="composer-footer"><span>Enter envia · Shift + Enter quebra a linha</span><span id="contextShort">Conversa nova</span></div></div> <div class="composer-area"><form id="messageForm" class="composer"><label class="sr-only" for="message">Mensagem para o agente</label><textarea id="message" rows="1" placeholder="Escreva uma mensagem para começar…" disabled></textarea><button id="sendButton" type="submit" class="send-button" aria-label="Enviar mensagem" disabled>↑</button></form><div class="composer-footer"><span>Enter envia · Shift + Enter quebra a linha</span><span id="contextShort">Conversa nova</span></div></div>
<details class="activity-panel"><summary>Atividade da tarefa <span id="eventCount">0 eventos</span></summary><ol id="activity"></ol><dl><dt>Contexto</dt><dd id="contextId">—</dd><dt>Tarefa</dt><dd id="taskId">—</dd></dl></details> <details class="activity-panel"><summary>Atividade da tarefa <span id="eventCount">0 eventos</span></summary><ol id="activity"></ol><dl><dt>Contexto</dt><dd id="contextId">—</dd><dt>Tarefa</dt><dd id="taskId">—</dd></dl></details>

View File

@@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#EA1B22" fill-rule="evenodd" d="M.1 8c0 2.761 2.237 5 4.997 5h5.806A4.999 4.999 0 0015.9 8c0-2.761-2.237-5-4.997-5H5.097A4.999 4.999 0 00.1 8zm13.911 0a3.235 3.235 0 01-3.234 3.237h-5.55A3.235 3.235 0 011.991 8a3.235 3.235 0 013.234-3.236h5.551A3.235 3.235 0 0114.011 8z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 531 B

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,7 @@ from agent_framework.global_supervisor import (
GlobalSupervisorRouter, GlobalSupervisorRouter,
InMemoryGlobalSessionStore, InMemoryGlobalSessionStore,
) )
from app.supervisor_llm import build_supervisor_llm from agent_framework.llm.providers import create_llm
from agent_framework.observability.observer import AgentObserver from agent_framework.observability.observer import AgentObserver
from agent_framework.security import install_authentication from agent_framework.security import install_authentication
@@ -38,7 +38,7 @@ app.add_middleware(
registry = BackendRegistry.from_yaml(settings.BACKENDS_CONFIG_PATH) registry = BackendRegistry.from_yaml(settings.BACKENDS_CONFIG_PATH)
analytics = create_analytics_publisher(settings) analytics = create_analytics_publisher(settings)
observer = AgentObserver(analytics=analytics) observer = AgentObserver(analytics=analytics)
llm = build_supervisor_llm(settings) if settings.GLOBAL_ROUTING_MODE in {"supervisor", "hybrid"} else None llm = create_llm(settings)
session_store = InMemoryGlobalSessionStore(ttl_seconds=settings.GLOBAL_SESSION_TTL_SECONDS) session_store = InMemoryGlobalSessionStore(ttl_seconds=settings.GLOBAL_SESSION_TTL_SECONDS)
router = GlobalSupervisorRouter( router = GlobalSupervisorRouter(
registry=registry, registry=registry,

View File

@@ -46,13 +46,6 @@ class GatewaySettings(BaseSettings):
LANGFUSE_HOST: str = "https://cloud.langfuse.com" LANGFUSE_HOST: str = "https://cloud.langfuse.com"
MODEL_PRICES_JSON: str | None = None MODEL_PRICES_JSON: str | None = None
USD_BRL_RATE: str = "5.0" USD_BRL_RATE: str = "5.0"
OCI_COMPARTMENT_ID: str | None = None
OCI_GENAI_PROJECT_OCID: str | None = None
OCI_AUTH_MODE: str = "config_file"
OCI_CONFIG_FILE: str = "~/.oci/config"
OCI_CONFIG_PROFILE: str = "DEFAULT"
OCI_REGION: str | None = None
# Analytics/Observer do próprio gateway. # Analytics/Observer do próprio gateway.
ENABLE_ANALYTICS: bool = False ENABLE_ANALYTICS: bool = False

View File

@@ -1,102 +0,0 @@
"""Gateway-only bootstrap for the Global Supervisor LLM.
Resolve the supervisor entry in llm_profiles.yaml *before* create_llm().
This matters because create_llm() selects OCISDKProvider immediately when
LLM_PROVIDER=oci_sdk; OCISDKProvider does not dynamically switch providers.
"""
from __future__ import annotations
import logging
import os
from pathlib import Path
import yaml
from agent_framework.llm.providers import create_llm
logger = logging.getLogger("agent_gateway.supervisor_llm")
def _profile_file(settings) -> Path:
explicit = (
os.getenv("GLOBAL_SUPERVISOR_LLM_PROFILES_PATH")
or getattr(settings, "GLOBAL_SUPERVISOR_LLM_PROFILES_PATH", None)
or os.getenv("LLM_PROFILES_PATH")
or getattr(settings, "LLM_PROFILES_PATH", None)
)
if explicit:
path = Path(str(explicit)).expanduser().resolve()
if not path.is_file():
raise RuntimeError(f"Supervisor LLM profile YAML not found: {path}")
return path
candidates = (
"config/llm_profiles.yaml",
"llm_profiles.yaml",
"apps/agent_gateway/config/llm_profiles.yaml",
"apps/agent_template_backend/config/llm_profiles.yaml",
"apps/agent_template_backend/llm_profiles.yaml",
)
for candidate in candidates:
path = Path(candidate)
if path.is_file():
return path.resolve()
raise RuntimeError(
"No llm_profiles.yaml found for Gateway supervisor. Set "
"GLOBAL_SUPERVISOR_LLM_PROFILES_PATH to the EXISTING YAML file path."
)
def build_supervisor_llm(settings):
"""Create LLM from the supervisor profile without mutating global settings."""
path = _profile_file(settings)
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
profile = (data.get("profiles") or {}).get("supervisor")
if not isinstance(profile, dict):
raise RuntimeError(f"Missing profiles.supervisor in {path}")
effective_provider = str(profile.get("provider") or settings.LLM_PROVIDER).strip()
allowed = {"oci_openai", "openai_compatible", "oci_sdk", "mock"}
if effective_provider not in allowed:
raise RuntimeError(f"Unsupported supervisor provider: {effective_provider}")
configured_url = (
profile.get("base_url")
or profile.get("service_endpoint")
or getattr(settings, "OCI_GENAI_BASE_URL", None)
)
url = str(configured_url or "").strip().rstrip("/")
if effective_provider == "oci_openai":
# OCI native URL is commonly used by the process-wide .env when the
# default provider is oci_sdk. Translate only the well-known OCI host.
if url.startswith("https://inference.generativeai.") and url.endswith(".oci.oraclecloud.com"):
url += "/openai/v1"
if not url.endswith("/openai/v1"):
raise RuntimeError(
f"Supervisor OpenAI-compatible base URL must end with /openai/v1, got {url!r}. "
"Set base_url under profiles.supervisor to the working endpoint."
)
key = profile.get("api_key") or getattr(settings, "OCI_GENAI_API_KEY", None) or os.getenv("OCI_GENAI_API_KEY")
if effective_provider in {"oci_openai", "openai_compatible"} and not key:
raise RuntimeError(
"Supervisor profile requires OCI_GENAI_API_KEY (or profiles.supervisor.api_key). "
"OCI SDK config_file signing is NOT an OpenAI-compatible bearer API key."
)
overrides = {
"LLM_PROVIDER": effective_provider,
"OCI_GENAI_MODEL": str(profile.get("model") or settings.OCI_GENAI_MODEL),
"OCI_GENAI_BASE_URL": url,
"LLM_TEMPERATURE": float(profile.get("temperature", settings.LLM_TEMPERATURE)),
"LLM_MAX_TOKENS": int(profile.get("max_tokens", settings.LLM_MAX_TOKENS)),
}
if key:
overrides["OCI_GENAI_API_KEY"] = str(key)
supervisor_settings = settings.model_copy(update=overrides)
logger.info(
"Supervisor LLM profile loaded path=%s provider=%s model=%s base_url=%s",
path, effective_provider, overrides["OCI_GENAI_MODEL"], url,
)
return create_llm(supervisor_settings)

View File

@@ -1,93 +0,0 @@
profiles:
default:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
max_tokens: 2048
supervisor:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 700
route_continuity:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 80
timeout_seconds: 5
processing_interruption_classifier:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 8
timeout_seconds: 5
router:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 768
guardrail:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 600
grl:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 700
judge:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 800
rag_rewriter:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 300
rag_compressor:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 1200
rag_generation:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.1
max_tokens: 1800
summary_memory:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.1
max_tokens: 1200
noc:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0
max_tokens: 700
billing_agent:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
product_agent:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
backoffice_agent:
provider: oci_openai
model: openai.gpt-4.1
temperature: 0.2
mcp_parameter_extraction:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 80
timeout_seconds: 5
transaction_parameter_extraction:
provider: oci_openai
model: openai.gpt-4.1-mini
temperature: 0
max_tokens: 500
timeout_seconds: 8

View File

@@ -0,0 +1,159 @@
# Generated by prepare_env.py. Do not commit.
ADB_DSN=oradb23ai_high
ADB_PASSWORD=Moniquinha19721972
ADB_TABLE_PREFIX=AGENTFW
ADB_USER=admin
ADB_WALLET_LOCATION=/app/wallet
ADB_WALLET_PASSWORD=Moniquinha1972
AGENT_PUBSUB_TOPIC=teste_pubsub
ANALYTICS_PROVIDERS=pubsub
API_HOST=0.0.0.0
API_PORT=8000
APP_ENV=oke-load-test
APP_NAME=ai-agent-template
CHECKPOINT_REPOSITORY_PROVIDER=autonomous
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
DEFAULT_CHANNEL=web
EMBEDDING_PROVIDER=oci
ENABLE_ANALYTICS=false
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
ENABLE_INPUT_GUARDRAILS=true
ENABLE_JUDGES=true
ENABLE_LANGFUSE=true
ENABLE_LANGFUSE_ANALYTICS_PUBLISHER=false
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
ENABLE_LLM_ROUTER=true
ENABLE_LONG_TERM_MEMORY=true
ENABLE_MCP_TOOLS=false
ENABLE_OCI_STREAMING=false
ENABLE_OTEL=false
ENABLE_OUTPUT_GUARDRAILS=true
ENABLE_OUTPUT_SUPERVISOR=true
ENABLE_PARALLEL_GUARDRAILS=true
ENABLE_REDIS_CACHE=false
ENABLE_ROUTE_STICKINESS=true
ENABLE_SUPERVISOR=true
ENABLE_TEXT_ADAPTER=true
ENABLE_VOICE_ADAPTER=true
ENABLE_WHATSAPP_ADAPTER=true
END_SESSION_MESSAGE='Atendimento encerrado. Obrigado pelo contato.'
FRAMEWORK_CHANNEL_INPUT_MODE=embedded
GCP_PROJECT_ID=project-7f829b68-bf43-442c-ad3
GCP_PUBSUB_TIMEOUT_SECONDS=30
GCP_PUBSUB_TOPIC=teste_pubsub
GCP_PUBSUB_TOPIC_PATH=projects/project-7f829b68-bf43-442c-ad3/topics/teste_pubsub
GOOGLE_APPLICATION_CREDENTIALS=/mnt/d/Dropbox/ORACLE/TIM/FY27/Testes/sa-hoshikawa.json
GRAPH_STORE_PROVIDER=autonomous
GUARDRAILS_CONFIG_PATH=./config/guardrails.yaml
GUARDRAILS_FAIL_FAST=true
HUMAN_HANDOFF_MESSAGE='Vou encaminhar seu atendimento para uma pessoa.'
IDENTITY_CONFIG_PATH=./config/identity.yaml
IMAGE_TAG=loadtest
JUDGES_CONFIG_PATH=./config/judges.yaml
K8S_NAMESPACE=agent-load-test
LANGFUSE_COMPACT_SUPPRESSED_PREFIXES=llm.chat_completion
LANGFUSE_COMPACT_VISIBLE_EVENT_PREFIXES='AGA.,NOC., IC.'
LANGFUSE_HOST=http://langfuse-web.langfuse.svc.cluster.local:3000
LANGFUSE_IGNORED_PATHS=/health,/ready,/metrics
LANGFUSE_IGNORE_HEALTHCHECKS=true
LANGFUSE_PUBLIC_KEY=pk-lf-2c82b991104f3f38f41878baecaaffd0
LANGFUSE_SECRET_KEY=sk-lf-defd21d2c6da3d4a9f301d993236958f9e56d94f9050a00f79067267b78a00a0
LANGFUSE_TRACE_MODE=compact
LLM_MAX_TOKENS=2048
LLM_PROVIDER=oci_sdk
LLM_TEMPERATURE=0.2
LLM_TIMEOUT_SECONDS=120
LOADTEST_AGENT_ID=telecom_contas
LOADTEST_CPU_LIMIT=2000m
LOADTEST_CPU_REQUEST=500m
LOADTEST_DURATION=10m
LOADTEST_MAX_REPLICAS=30
LOADTEST_MAX_VUS=1000
LOADTEST_MEMORY_LIMIT=2Gi
LOADTEST_MEMORY_REQUEST=768Mi
LOADTEST_MESSAGE='Explique em uma frase o que é uma fatura de telecom.'
LOADTEST_MIN_REPLICAS=4
LOADTEST_RPS=100
LOADTEST_SCENARIO=unique_sessions
LOADTEST_TARGET=http://agent-template-backend-lb.agent-load-test.svc.cluster.local:8000
LOADTEST_TENANT_ID=loadtest
LOADTEST_VUS=250
LOG_LEVEL=INFO
LONG_TERM_MEMORY_AUTO_EXTRACT=true
LONG_TERM_MEMORY_INJECT_CONTEXT=true
LONG_TERM_MEMORY_MAX_CONTEXT_ITEMS=20
LONG_TERM_MEMORY_MIN_CONFIDENCE=0.70
LONG_TERM_MEMORY_PROVIDER=sqlite
LONG_TERM_MEMORY_SQLITE_PATH=./data/agent_framework.db
LONG_TERM_MEMORY_TABLE=agentfw_long_term_memory
MCP_GATEWAY_ENABLED=false
MCP_PARAMETER_MAPPING_PATH=./config/mcp_parameter_mapping.yaml
MCP_SERVERS_CONFIG_PATH=./config/mcp_servers.yaml
MCP_TOOL_TIMEOUT_SECONDS=30
MEMORY_CONTEXT_STRATEGY=summary
MEMORY_HISTORY_LIMIT=80
MEMORY_INJECT_RECENT_MESSAGES=true
MEMORY_INJECT_SUMMARY=true
MEMORY_MAX_SUMMARY_CHARS=6000
MEMORY_RECENT_MESSAGES_LIMIT=8
MEMORY_REPOSITORY_PROVIDER=autonomous
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
MEMORY_SUMMARY_USE_LLM=true
MONGODB_DATABASE=agent_platform
MONGODB_URI='mongodb://user:password@autonomous-private-endpoint:27017/?tls=true'
OCIR_REPOSITORY_PREFIX=agent-framework-loadtest
OCI_AUTH_MODE=oke_workload_identity
OCI_CLI_PROFILE=LATINOAMERICA-SaoPaulo
OCI_COMPARTMENT_ID=ocid1.compartment.oc1..aaaaaaaaexpiw4a7dio64mkfv2t273s2hgdl6mgfvvyv7tycalnjlvpvfl3q
OCI_CONFIG_FILE='~/.oci/config'
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
OCI_GENAI_API_KEY=sk-ph3FgX6iP3fxAQCXb9IpPIDTadkeeYAWntUWhzcWysIM6zsS
OCI_GENAI_BASE_URL=https://inference.generativeai.us-chicago-1.oci.oraclecloud.com
OCI_GENAI_MODEL=openai.gpt-4.1
OCI_GENAI_PROJECT_OCID=''
OCI_PROFILE=LATINOAMERICA-Chicago
OCI_REGION=sa-saopaulo-1
OCI_REGION_KEY=gru
OCIR_USERNAME=idi1o0a010nx/oracleidentitycloudservice/cristiano.hoshikawa@oracle.com
OCIR_AUTH_TOKEN="A5XBm>8gvA2oDy;it1lq"
OCIR_EMAIL=cristiano.hoshikawa@oracle.com
OCIR_PULL_SECRET_NAME=ocir-secret
OCI_STREAM_ENDPOINT=''
OCI_STREAM_OCID=''
OCI_STREAM_PARTITION_KEY=agent-events
OCI_TENANCY_NAMESPACE=idi1o0a010nx
OKE_CLUSTER_OCID=ocid1.cluster.oc1.sa-saopaulo-1.aaaaaaaabpismmmj6kwo4idomzso5fr32xlnbjcm74pfa7bfhc52plqe64oq
EXISTING_LB_OCID=ocid1.loadbalancer.oc1.sa-saopaulo-1.aaaaaaaaklbmmriyabvxradqkchy5cobelzxj37rn27p2rsfef5uez54jfqa
BACKEND_SET_NAME=agent-framework-loadtest
BACKEND_LISTENER_NAME=agent-framework-loadtest
BACKEND_LISTENER_PORT=8000
BACKEND_LISTENER_PROTOCOL=HTTP
BACKEND_HEALTH_PATH=/health
OTEL_EXPORTER_OTLP_ENDPOINT=''
OTEL_SERVICE_NAME=ai-agent-template
OUTPUT_SUPERVISOR_MAX_RETRIES=3
PROJECT_ID=project-7f829b68-bf43-442c-ad3
PROMPT_POLICY_PATH=./config/prompt_policy.yaml
PUBSUB_SEQUENCE_ENABLED=true
PUBSUB_SEQUENCE_KEY_PREFIX=observer:sequence
PUBSUB_SEQUENCE_MEMORY_FALLBACK=false
PUBSUB_SEQUENCE_MONGODB_COLLECTION=observer_event_counters
PUBSUB_SEQUENCE_MONGODB_DATABASE=agent_platform
PUBSUB_SEQUENCE_MONGODB_URI='mongodb://user:password@autonomous-private-endpoint:27017/?tls=true'
PUBSUB_SEQUENCE_PROVIDER=mongodb
PUBSUB_SEQUENCE_TTL_SECONDS=86400
PUBSUB_TOPIC_PATH=projects/project-7f829b68-bf43-442c-ad3/topics/teste_pubsub
RAG_FILE_GLOBS='*.md,*.txt,*.yaml,*.yml,*.json'
RAG_TOP_K=5
REDIS_URL=redis://localhost:6379/0
ROUTE_STICKINESS_CONFIDENCE_THRESHOLD=0.90
ROUTE_STICKINESS_HISTORY_TURNS=2
ROUTE_STICKINESS_LLM_PROFILE=route_continuity
ROUTE_STICKINESS_MAX_TOKENS=80
ROUTING_CONFIG_PATH=./config/routing.yaml
ROUTING_MODE=router
SESSION_REPOSITORY_PROVIDER=autonomous
SIMULATE_AGA_MISSING_TRANSACTION_ID=true
TOOLS_CONFIG_PATH=./config/tools.yaml
USAGE_REPOSITORY_PROVIDER=autonomous
VECTOR_STORE_PROVIDER=autonomous

View File

@@ -1,180 +0,0 @@
# Framework Agents with Asynchronous A2A
## What was implemented
An agent created from `templates/agent_template_backend` can expose both the native protocol and A2A in the same process. The Agent Gateway also provides the same A2A interface. Both interfaces use the official SDK and its Tasks/SSE operations, with A2A 1.0 and 0.3 interoperability.
```text
A2A consumer ── A2A Gateway (Task)
├── native agent (/gateway/message)
└── framework A2A agent (remote Task)
A2A consumer ── framework A2A agent directly
```
Immediate submission returns a Task before the business operation finishes. Execution continues after the HTTP response and even after the SSE connection is closed. The consumer can retrieve the result or reconnect to an in-progress task. Native endpoints retain their behavior: `/gateway/message` still waits for the result, and native SSE remains separate from A2A SSE.
## Enable A2A in an agent built with the framework
From the framework root, install the optional dependency:
```sh
python -m pip install -e 'libs/agent_framework[a2a]' -r templates/agent_template_backend/requirements.txt
```
In the backend environment:
```dotenv
ENABLE_A2A=true
A2A_TASK_MODE=true
A2A_PUBLIC_URL=http://localhost:8000
A2A_TASK_TIMEOUT_SECONDS=240
```
Start the backend with its usual settings. For example, after preparing the `.env` and agent configuration files:
```sh
cd templates/agent_template_backend
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
```
Use the actual public address in `A2A_PUBLIC_URL`. A sample configuration is provided in `.env.a2a.example`. An optional image can also be built from the framework root:
```sh
docker build -f templates/agent_template_backend/Dockerfile.a2a -t framework-agent-a2a .
```
Profiles defined in `agents.yaml` are published as skills in the Agent Card: `GET /.well-known/agent-card.json`. Use `message.metadata.agent_id` to select a profile; otherwise, the backend retains its default selection.
## Register the agent with the Agent Gateway
```yaml
backends:
pedidos_a2a:
protocol: a2a
a2a_version: '1.0'
url: http://backend-pedidos:8000
a2a_rpc_path: /a2a
description: Order inquiry and tracking
keywords: [pedido, entrega, rastreamento]
default_agent_id: pedidos
```
On the gateway, enable `ENABLE_A2A=true` and `A2A_TASK_MODE=true`, and configure its own `A2A_PUBLIC_URL`. For longer-running operations, adjust the limits; for example, use `A2A_TASK_TIMEOUT_SECONDS=300` and `BACKEND_TIMEOUT_SECONDS=270`, with the backend timeout set to 240 seconds. The effective duration is limited by the shortest applicable deadline.
The gateway immediately returns its own Task and polls the remote Task until completion or until input/authentication is required. Remote status events are published as `a2a.backend.status`. Local and remote IDs may differ. The native result includes `metadata.a2a_task_id` and `metadata.a2a_context_id` for correlation; external consumers continue tracking the ID issued by the gateway.
## Asynchronous A2A 1.0 submission
Send a `POST /a2a` request with `Content-Type: application/json` and `A2A-Version: 1.0`:
```json
{
"jsonrpc": "2.0",
"id": "request-1",
"method": "SendMessage",
"params": {
"configuration": {"returnImmediately": true},
"message": {
"messageId": "message-1",
"contextId": "conversation-123",
"role": "ROLE_USER",
"parts": [{"text": "Check my order"}],
"metadata": {"agent_id": "pedidos", "tenant_id": "default"}
}
}
}
```
The response contains `result.task.id`, `contextId`, and an initial state such as `TASK_STATE_SUBMITTED` or `TASK_STATE_WORKING`. Save that ID and query the task:
```json
{
"jsonrpc": "2.0",
"id": "request-2",
"method": "GetTask",
"params": {"id": "TASK_ID"}
}
```
The result is in `result`. When `status.state` is `TASK_STATE_COMPLETED`, the final content is in `artifacts[].parts`. It includes a text part and a `data` part containing the original framework response.
Without `returnImmediately: true`, submission waits for a final or interrupted state and returns a Task in the same format. To preserve the behavior of the initial A2A release, which returned a Message, set `A2A_TASK_MODE=false`. In that mode, streaming is disabled and there are no local Tasks.
## Streaming and reconnection
To submit and follow a task, use `SendStreamingMessage` instead of `SendMessage`, with the same parameters. The response uses `Content-Type: text/event-stream`. Each `data:` field contains a JSON-RPC response. Events include:
1. The initial Task.
2. Status updates (submitted/working) and progress events.
3. The response artifact.
4. A completed, failed, canceled, input-required, or auth-required state.
The framework backend publishes milestones such as `flow.start`, `session.upserted`, `workflow.started`, `workflow.completed`, and `message.responded` when the flow reaches those stages. They appear in `result.statusUpdate.metadata.framework_event`, with corresponding data in `framework_payload`. This does not imply LLM token streaming.
To reconnect to an in-progress Task:
```json
{
"jsonrpc": "2.0",
"id": "request-3",
"method": "SubscribeToTask",
"params": {"id": "TASK_ID"}
}
```
Reconnection delivers the current state and subsequent events; replay of all missed events is not guaranteed. If the task has already finished, use `GetTask`.
## Cancellation, failures, and continuity
`CancelTask`, with `params.id`, cancels the ongoing local execution. The SDK interrupts the coroutine and records `TASK_STATE_CANCELED`. This does not undo effects already committed in tools or external systems. When the gateway is polling a remote agent, stopping the local task does not guarantee cancellation of an operation already accepted by the remote service.
Exceptions and expiration of `A2A_TASK_TIMEOUT_SECONDS` result in `TASK_STATE_FAILED`, which can be queried later and carries a generic message. Internal details remain in server logs.
For an interaction requiring further input, a handler can return `metadata.a2a_task_state: "input-required"` or `"auth-required"`. The consumer sends another message with the same `taskId` and `contextId`. The task is resumed; a completed task cannot be re-executed. The output adapter preserves continuity for remote tasks in these states.
Native workflows that do not declare such a state complete one Task per turn. The conversation continues with a new Task and the same `contextId`, using the framework's existing sessions and memories.
## A2A 0.3 equivalents
Use `A2A-Version: 0.3`, `role: "user"`, and the 0.3 `kind` discriminators.
| A2A 1.0 | A2A 0.3 |
| --- | --- |
| `SendMessage` + `returnImmediately: true` | `message/send` + `blocking: false` |
| `SendStreamingMessage` | `message/stream` |
| `GetTask` | `tasks/get` |
| `SubscribeToTask` | `tasks/resubscribe` |
| `CancelTask` | `tasks/cancel` |
In 0.3, `result` directly contains the Task without the `task` envelope. States use `submitted`, `working`, `completed`, `failed`, etc.
## Storage and authentication
By default, the SDK uses an in-memory TaskStore. Tasks and results are lost when the process restarts. With multiple workers/replicas, use request affinity to the same process during execution or configure shared storage and coordination. A persistent TaskStore alone does not make execution resumable after a failure. The `install_a2a` and `install_backend_a2a` functions accept `task_store` for integration with SDK storage.
The optional backend reuses `AGENT_AUTH_*`; the gateway uses `AGENT_GATEWAY_AUTH_*`. The identity authenticated by middleware determines Task ownership, including retrieval and cancellation. Anonymous users share the anonymous scope. `tenant_id` in metadata remains request data, not a credential or authorization isolation mechanism. Clients sharing the same credential/principal also share a task scope.
`A2A_SECURITY_SCHEMES` and `A2A_SECURITY` document mechanisms in the Agent Card, as described in the general guide. SSE is enabled in Task mode; push notifications and gRPC are outside the scope of this implementation.
## Validation
The test suite covers immediate submission while the handler remains blocked, polling, streaming with progress and results, cancellation, failures, timeouts, resumption, isolation between authenticated identities, and a gateway calling a real A2A backend in a local application. HTTP tests on `127.0.0.1` also verify that the first event arrives before completion and that reconnection works after disconnection.
```sh
python -m pip install pytest pytest-asyncio uvicorn
PYTHONPATH=libs/agent_framework/src pytest \
tests/test_a2a_interoperability.py tests/test_a2a_async_tasks.py \
tests/test_a2a_http_streaming.py \
tests/unit/test_sse.py tests/unit/test_sse_replay_dedup.py \
tests/unit/test_authentication.py tests/unit/test_authentication_policies.py -q
```
The tests do not access OCI or external business services. In environments where binding to local ports is not permitted, run the HTTP test in an environment that allows binding to loopback.
Official specification: https://a2a-protocol.org/latest/specification/
Official SDK: https://github.com/a2aproject/a2a-python
## A2A-ready frontend
The `apps/agent_frontend_a2a` interface consumes this protocol directly, supporting Tasks, streaming, retrieval, and cancellation. See [A2A Frontend](../apps/agent_frontend_a2a/README.md) for the demonstration without OCI and startup commands using the backend or Gateway. The native frontend remains in `apps/agent_frontend`.

View File

@@ -29,7 +29,7 @@ python -m pip install -e 'libs/agent_framework[a2a]' -r apps/agent_gateway/requi
export ENABLE_A2A=true export ENABLE_A2A=true
export A2A_PUBLIC_URL=https://meu-gateway.example export A2A_PUBLIC_URL=https://meu-gateway.example
export BACKENDS_CONFIG_PATH="$PWD/apps/agent_gateway/config/backends.a2a.example.yaml" export BACKENDS_CONFIG_PATH="$PWD/apps/agent_gateway/config/backends.a2a.example.yaml"
python -m uvicorn app.main:app --app-dir apps/agent_gateway --env-file .env --port 8010 python -m uvicorn app.main:app --app-dir apps/agent_gateway --port 8010
``` ```
Edite primeiro as URLs do catálogo de exemplo. Para agentes externos, informe o Edite primeiro as URLs do catálogo de exemplo. Para agentes externos, informe o

View File

@@ -1,105 +0,0 @@
# A2A Integration in Agent Framework OCI
## Architecture decision
The existing Agent Gateway gains an optional A2A interface. There is no need to maintain a second router, duplicate the supervisor, or migrate existing agents.
```text
Existing consumer ── /gateway/message ─┐
├─ Agent Gateway / supervisor ─┬─ native backend
A2A consumer ─────── /a2a ─────────────┘ └─ A2A backend
```
The native backend continues receiving `{channel, payload, agent_id, tenant_id}`. Omitting `protocol` from the catalog preserves `native`. A2A is disabled by default; the SDK is imported only when the interface is enabled or an A2A backend is selected. The agent runtime, workflows, channels, and MCPs were not changed.
The inbound interface uses A2A **1.0 / JSON-RPC**, the official `a2a-sdk==1.2.2`, with a 0.3 adapter provided by the SDK itself. External agents can be registered with `a2a_version: '1.0'` or `'0.3'`; there is no heuristic version detection.
## Local setup
From the framework root:
```sh
python -m pip install -e 'libs/agent_framework[a2a]' -r apps/agent_gateway/requirements.txt
export ENABLE_A2A=true
export A2A_PUBLIC_URL=https://meu-gateway.example
export BACKENDS_CONFIG_PATH="$PWD/apps/agent_gateway/config/backends.a2a.example.yaml"
python -m uvicorn app.main:app --app-dir apps/agent_gateway --env-file .env --port 8010
```
First, edit the URLs in the sample catalog. For external agents, specify the JSON-RPC endpoint advertised in the Agent Card as `url` + `a2a_rpc_path`. Credentials are stored in environment variables referenced by `a2a_headers_env`. Their values are not exposed through `/backends`.
An optional image can be built from the framework root:
```sh
docker build -f apps/agent_gateway/Dockerfile.a2a -t agent-gateway-a2a .
```
Set `A2A_PUBLIC_URL` in the container environment and mount the backend catalog. The existing deployment images and commands remain available.
## Discovery and invocation
The Agent Card is available at `GET /.well-known/agent-card.json`; skills are derived from enabled backends. Messages arrive at `POST /a2a`.
A2A 1.0 example, with the `A2A-Version: 1.0` header:
```json
{
"jsonrpc": "2.0",
"id": "request-1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "message-1",
"contextId": "conversation-123",
"role": "ROLE_USER",
"parts": [
{"text": "I want to check my bill"},
{"data": {"customer_id": "123"}}
],
"metadata": {"tenant_id": "default"}
}
}
}
```
In Task mode (the default), the response is in `result.task`. The business result is in `artifacts[].parts`, with text and a `data` part containing the framework response. With `A2A_TASK_MODE=false`, it remains in `result.message`. Reuse `contextId` on subsequent turns. In 0.3 mode, use the `A2A-Version: 0.3` header, the `message/send` method, the `user` role, and the `kind` fields from the 0.3 specification; the result is a Task directly (or a Message with `A2A_TASK_MODE=false`).
`data` parts must contain objects and are incorporated into the business payload. Session identity and protocol text take precedence over those data. Files received through the interface are explicitly rejected.
## Conversations, tasks, and limits
- The interface creates **A2A Tasks** and supports immediate submission, retrieval, cancellation, and SSE streaming. `A2A_TASK_MODE=false` preserves the previous Message mode. See [Asynchronous A2A](A2A_ASSINCRONO.md) for complete examples.
- The output adapter accepts a Message or a Task; it polls `GetTask`/`tasks/get` while the agent is working, within an overall timeout configured by `BACKEND_TIMEOUT_SECONDS`. It does not automatically retry submission operations.
- Tasks requiring input/authentication preserve state and identity for the next turn. Completed tasks are not reused.
- Remote continuity is stored in-process by tenant/backend/session, with a one-hour lifetime and a limit of 10,000 entries. With multiple replicas, use session affinity or implement shared storage before relying on this continuity. Process restarts clear the cache.
- `metadata.a2a_result` preserves the remote result, including data/artifacts; `a2a_context_id`, `a2a_task_id`, and `a2a_state` allow explicit inspection and continuity in the payload when needed.
- SSE streaming is available in Task mode. Push notifications and gRPC are not provided. The native event proxy is not used for A2A agents: use `SendStreamingMessage`/`SubscribeToTask` and their 0.3 equivalents.
- The `templates/agent_template_backend` template also exposes A2A directly when `ENABLE_A2A=true`, without going through the global gateway.
## Authentication
A2A routes share the authentication middleware already installed in the gateway (`AGENT_GATEWAY_AUTH_*`). Enabling A2A does not automatically enable authentication. The tenant in metadata retains the framework's current semantics: it is request data, not an authorization granted by the protocol. Tenant access policies remain the responsibility of the authentication/authorization configuration.
When authentication is enabled, also declare it in the Agent Card, for example:
```sh
export A2A_SECURITY_SCHEMES='{"gatewayBearer":{"httpAuthSecurityScheme":{"scheme":"bearer"}}}'
export A2A_SECURITY='[{"gatewayBearer":[]}]'
```
These declarations document the mechanism; the middleware validates credentials. If public discovery is desired, add only the Agent Card path to the public-path configuration; `/a2a` remains protected.
## Validation
Tests in `tests/test_a2a_interoperability.py` cover both input formats, the native contract, both output formats, polling, JSON-RPC errors, task continuity, tenant isolation, and timeouts.
```sh
PYTHONPATH=libs/agent_framework/src pytest tests/test_a2a_interoperability.py -q
```
Validation uses simulated HTTP transports and a local FastAPI application; it does not replace integration testing with OCI services and external agents in the deployment environment.
Official references:
- https://a2a-protocol.org/latest/specification/
- https://a2a-protocol.org/v0.3.0/specification/
- https://github.com/a2aproject/a2a-python

View File

@@ -38,4 +38,4 @@ requires = ["setuptools>=80", "wheel>=0.45"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.setuptools.package-data] [tool.setuptools.package-data]
"agent_framework" = ["locales/*.json", "config/*.yaml", "guardrails/calibrated/capabilities/*.yaml"] "agent_framework" = ["config/*.yaml", "guardrails/calibrated/capabilities/*.yaml"]

View File

@@ -24,5 +24,3 @@ Requires-Dist: motor>=3.6.0
Requires-Dist: google-cloud-pubsub>=2.28.0 Requires-Dist: google-cloud-pubsub>=2.28.0
Requires-Dist: mcp>=1.9.0 Requires-Dist: mcp>=1.9.0
Requires-Dist: PyJWT[crypto]>=2.9.0 Requires-Dist: PyJWT[crypto]>=2.9.0
Provides-Extra: a2a
Requires-Dist: a2a-sdk[http-server]==1.2.2; extra == "a2a"

View File

@@ -3,7 +3,6 @@ src/agent_framework/__init__.py
src/agent_framework/extensions.py src/agent_framework/extensions.py
src/agent_framework/gateway_policy_context.py src/agent_framework/gateway_policy_context.py
src/agent_framework/idempotency.py src/agent_framework/idempotency.py
src/agent_framework/localization.py
src/agent_framework/observer.py src/agent_framework/observer.py
src/agent_framework/runtime_mcp_gateway_adapter.py src/agent_framework/runtime_mcp_gateway_adapter.py
src/agent_framework.egg-info/PKG-INFO src/agent_framework.egg-info/PKG-INFO
@@ -11,10 +10,6 @@ src/agent_framework.egg-info/SOURCES.txt
src/agent_framework.egg-info/dependency_links.txt src/agent_framework.egg-info/dependency_links.txt
src/agent_framework.egg-info/requires.txt src/agent_framework.egg-info/requires.txt
src/agent_framework.egg-info/top_level.txt src/agent_framework.egg-info/top_level.txt
src/agent_framework/a2a/__init__.py
src/agent_framework/a2a/client.py
src/agent_framework/a2a/progress.py
src/agent_framework/a2a/server.py
src/agent_framework/analytics/__init__.py src/agent_framework/analytics/__init__.py
src/agent_framework/analytics/composite_publisher.py src/agent_framework/analytics/composite_publisher.py
src/agent_framework/analytics/event_builder.py src/agent_framework/analytics/event_builder.py
@@ -44,10 +39,6 @@ src/agent_framework/config/__init__.py
src/agent_framework/config/agent_registry.py src/agent_framework/config/agent_registry.py
src/agent_framework/config/observability_mapping.yaml src/agent_framework/config/observability_mapping.yaml
src/agent_framework/config/settings.py src/agent_framework/config/settings.py
src/agent_framework/decisions/__init__.py
src/agent_framework/decisions/client.py
src/agent_framework/decisions/adapters/__init__.py
src/agent_framework/decisions/adapters/typesafe.py
src/agent_framework/events/__init__.py src/agent_framework/events/__init__.py
src/agent_framework/events/oci_streaming.py src/agent_framework/events/oci_streaming.py
src/agent_framework/gateways/__init__.py src/agent_framework/gateways/__init__.py
@@ -147,8 +138,6 @@ src/agent_framework/llm/profile_resolver.py
src/agent_framework/llm/providers.py src/agent_framework/llm/providers.py
src/agent_framework/llm/structured_output.py src/agent_framework/llm/structured_output.py
src/agent_framework/llm/types.py src/agent_framework/llm/types.py
src/agent_framework/locales/en.json
src/agent_framework/locales/pt-BR.json
src/agent_framework/mcp/__init__.py src/agent_framework/mcp/__init__.py
src/agent_framework/mcp/client.py src/agent_framework/mcp/client.py
src/agent_framework/mcp/models.py src/agent_framework/mcp/models.py
@@ -185,7 +174,6 @@ src/agent_framework/observability/noc_events.py
src/agent_framework/observability/noc_otel.py src/agent_framework/observability/noc_otel.py
src/agent_framework/observability/observer.py src/agent_framework/observability/observer.py
src/agent_framework/observability/otel.py src/agent_framework/observability/otel.py
src/agent_framework/observability/severity_policy.py
src/agent_framework/observability/streaming_events.py src/agent_framework/observability/streaming_events.py
src/agent_framework/observability/streaming_exporter.py src/agent_framework/observability/streaming_exporter.py
src/agent_framework/observability/telemetry.py src/agent_framework/observability/telemetry.py

View File

@@ -19,6 +19,3 @@ motor>=3.6.0
google-cloud-pubsub>=2.28.0 google-cloud-pubsub>=2.28.0
mcp>=1.9.0 mcp>=1.9.0
PyJWT[crypto]>=2.9.0 PyJWT[crypto]>=2.9.0
[a2a]
a2a-sdk[http-server]==1.2.2

View File

@@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from agent_framework.decisions import invoke as decision_invoke, predicate
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any from typing import Any
@@ -57,11 +55,8 @@ async def classify_processing_interruption(
f"Complemento/interrupção atual: {supplement_client}\n" f"Complemento/interrupção atual: {supplement_client}\n"
) )
try: try:
response = await decision_invoke(llm, response = await llm.ainvoke(
[{"role": "system", "content": prompt}], [{"role": "system", "content": prompt}],
decision_point="processing_interruption",
decision_questions=[predicate("regenerate", "Does the interruption require regenerating the response?")],
decision_render=lambda d: "1" if d["regenerate"] else "0",
temperature=0, temperature=0,
fallback_max_tokens=8, fallback_max_tokens=8,
profile_name=profile_name, profile_name=profile_name,

View File

@@ -21,9 +21,6 @@ class Settings(BaseSettings):
A2A_SECURITY_SCHEMES: dict = Field(default_factory=dict) A2A_SECURITY_SCHEMES: dict = Field(default_factory=dict)
A2A_SECURITY: list[dict[str, list[str]]] = Field(default_factory=list) A2A_SECURITY: list[dict[str, list[str]]] = Field(default_factory=list)
AGENT_LOCALE: str = 'pt-BR'
AGENT_LOCALES_DIR: str | None = None
APP_NAME: str = 'ai-agent-template' APP_NAME: str = 'ai-agent-template'
APP_ENV: str = 'local' APP_ENV: str = 'local'
LOG_LEVEL: str = 'INFO' LOG_LEVEL: str = 'INFO'
@@ -285,7 +282,3 @@ def get_settings() -> Settings:
return Settings() return Settings()
settings = get_settings() settings = get_settings()
# Fail early for invalid locale catalogs rather than during a transaction.
from agent_framework.localization import catalog
catalog(settings.AGENT_LOCALE)

View File

@@ -1,2 +0,0 @@
"""Opt-in typed decision providers. Generative tasks keep their existing provider."""
from .client import choice, predicate, score, invoke, selected, evaluate

View File

@@ -1 +0,0 @@
"""Optional decision-provider adapters; no changes to the default engine."""

View File

@@ -1,97 +0,0 @@
"""Isolated TypeSafe API adapter. Uses the verified `noul` wire primitive.
Choice is implemented as independent binary evaluations with strict rejection of
ambiguous outcomes. This is an adapter strategy, NOT a native TypeSafe choice
contract. The framework's original decision engine is never changed.
"""
from __future__ import annotations
import os
import logging
import json
logger = logging.getLogger("agent_framework.decisions")
class TypeSafeContractError(ValueError):
def __init__(self, code: str):
self.code = code
super().__init__(code)
def _wire_name(index: int, option: int | None = None) -> str:
return f"__jev_q{index}" if option is None else f"__jev_q{index}_o{option}"
def build_request(state: str, questions: list[dict]) -> dict:
converted = {}
for i, question in enumerate(questions):
kind = question["type"]
if kind == "predicate":
converted[_wire_name(i)] = {"type": "noul", "instructions": question["instructions"]}
elif kind == "choice":
options = question["choices"]
if not options:
raise TypeSafeContractError("EMPTY_CHOICE")
for j, option in enumerate(options):
# Do not interpolate the user's state into the instruction.
converted[_wire_name(i, j)] = {
"type": "noul",
"instructions": (
f"Assess whether '{option['value']}' is the correct selection for "
f"the decision described here: {question['instructions']}. "
"Evaluate the supplied state and policy; answer yes only if this "
"specific option is appropriate."
),
}
elif kind == "score":
# The direct API score contract has not been verified. Fail closed.
raise TypeSafeContractError("UNSUPPORTED_DIRECT_SCORE")
else:
raise TypeSafeContractError("UNSUPPORTED_QUESTION_TYPE")
return {"model": os.getenv("JEV_MODEL", "jev-latest"), "state": state, "questions": converted}
def _probability(answer: object) -> float:
if not isinstance(answer, dict) or answer.get("type") not in (None, "noul", "predicate"):
raise TypeSafeContractError("INVALID_NOUL_ANSWER")
value = answer.get("noul", answer.get("probability"))
if isinstance(value, bool) or not isinstance(value, (int, float)) or not 0 <= value <= 1:
raise TypeSafeContractError("INVALID_NOUL_PROBABILITY")
return float(value)
def normalize_answers(response: dict, questions: list[dict]) -> list[dict]:
if not isinstance(response, dict) or not isinstance(response.get("answers"), dict):
raise TypeSafeContractError("INVALID_ANSWERS_CONTAINER")
answers = response["answers"]
expected = set()
for i, q in enumerate(questions):
if q["type"] == "choice":
expected.update(_wire_name(i, j) for j in range(len(q["choices"])))
else:
expected.add(_wire_name(i))
if set(answers) != expected:
raise TypeSafeContractError("MISSING_OR_EXTRA_ANSWER")
result = []
for i, q in enumerate(questions):
kind = q["type"]
if kind == "predicate":
result.append({"name": q["name"], "type": "predicate", "probability": _probability(answers[_wire_name(i)])})
elif kind == "choice":
probabilities = [_probability(answers[_wire_name(i, j)]) for j in range(len(q["choices"]))]
ranked = sorted(range(len(probabilities)), key=lambda j: probabilities[j], reverse=True)
winner, runner_up = probabilities[ranked[0]], probabilities[ranked[1]] if len(ranked) > 1 else 0.0
# Require a single positive, unambiguous choice. Low confidence
# remains subject to the unchanged JEV_MIN_CONFIDENCE in client.py.
if os.getenv("JEV_DECISION_DIAGNOSTICS", "false").strip().lower() == "true":
logger.info("jev.choice.diagnostics question_index=%d probabilities=%s winner_index=%d winner=%.4f runner_up=%.4f margin=%.4f status=%s",
i, json.dumps([round(v, 4) for v in probabilities]), ranked[0],
winner, runner_up, winner - runner_up,
"ambiguous" if winner <= 0.5 or winner - runner_up < 0.15 else "candidate")
if winner <= 0.5 or winner - runner_up < 0.15:
raise TypeSafeContractError("AMBIGUOUS_DIRECT_CHOICE")
result.append({"name": q["name"], "type": "choice",
"choice": q["choices"][ranked[0]]["value"], "confidence": winner,
"option_probabilities": probabilities})
else:
raise TypeSafeContractError("UNSUPPORTED_DIRECT_QUESTION_TYPE")
return result

View File

@@ -1,298 +0,0 @@
from __future__ import annotations
import asyncio
import json
import logging
import math
import os
import re
import time
import urllib.request
import urllib.error
import urllib.parse
from typing import Any
logger = logging.getLogger("agent_framework.decisions")
def env_key(point: str) -> str:
return "DECISION_" + re.sub(r"[^A-Z0-9]+", "_", point.upper()).strip("_") + "_PROVIDER"
def selected(point: str, group: str | None = None) -> bool:
default = os.getenv(env_key(group), os.getenv("DECISION_PROVIDER", "current")) if group else os.getenv("DECISION_PROVIDER", "current")
value = os.getenv(env_key(point), default).strip().lower()
if value not in {"current", "jev"}:
raise ValueError(f"{env_key(point)} must be current or jev")
return value == "jev"
def choice(name: str, values: list, instructions: str = "") -> dict:
values = list(dict.fromkeys(str(v) for v in values))
return {"type": "choice", "name": name, "instructions": instructions or f"Select {name} following the supplied policy.",
"choices": [{"value": v, "description": v} for v in values]}
def predicate(name: str, instructions: str = "") -> dict:
return {"type": "predicate", "name": name, "instructions": instructions or f"Is {name} true under the supplied policy?"}
def score(name: str, instructions: str = "") -> dict:
return {"type": "score", "name": name, "instructions": instructions or "Apply the supplied rubric on a 0 to 10 scale.",
"levels": [{"label": str(i)} for i in range(11)]}
def _number(value: Any, low: float, high: float) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or not low <= value <= high:
raise _validation_error("INVALID_PROBABILITY_OR_SCORE")
return float(value)
def _jev_endpoint() -> str:
"""Return a trusted HTTPS decision endpoint configured by the deployer.
The endpoint is an operator setting, never derived from model output.
Set JEV_ALLOWED_HOSTS in production to restrict credential destinations.
"""
endpoint = os.getenv("JEV_ENDPOINT", "https://ai-gateway.vercel.sh/v1/decisions").strip()
parsed = urllib.parse.urlsplit(endpoint)
if (parsed.scheme != "https" or not parsed.hostname or parsed.username or
parsed.password or parsed.fragment or not parsed.path):
raise ValueError("JEV_ENDPOINT must be a valid HTTPS endpoint URL")
allowed = [host.strip().lower() for host in os.getenv(
"JEV_ALLOWED_HOSTS", "ai-gateway.vercel.sh,api.typesafe.ai").split(",") if host.strip()]
if parsed.hostname.lower() not in allowed:
raise ValueError("JEV_ENDPOINT host is not in JEV_ALLOWED_HOSTS")
return endpoint
def _post(body: dict) -> dict:
key = (os.getenv("JEV_API_KEY") or os.getenv("AI_GATEWAY_API_KEY") or "").strip()
if not key:
raise ValueError("JEV_API_KEY or AI_GATEWAY_API_KEY is required for JEV")
timeout = float(os.getenv("JEV_TIMEOUT_SECONDS", "15"))
if not math.isfinite(timeout) or timeout <= 0:
raise ValueError("JEV_TIMEOUT_SECONDS must be positive")
request = urllib.request.Request(_jev_endpoint(),
data=json.dumps(body, ensure_ascii=False).encode("utf-8"), method="POST",
headers={"Authorization": "Bearer " + key, "Content-Type": "application/json"})
# urllib does not automatically follow redirects for POST in all cases;
# disable redirects to avoid sending credentials to another host.
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
opener = urllib.request.build_opener(NoRedirect)
with opener.open(request, timeout=timeout) as response:
return json.load(response)
def _safe_http_diagnostic(exc: urllib.error.HTTPError) -> str:
"""Extract only structural validation hints; never log echoed inputs or tokens."""
if exc.code not in (400, 422):
return "unavailable"
try:
raw = exc.read(16384)
payload = json.loads(raw)
except (ValueError, TypeError, OSError, UnicodeError):
return "unavailable"
if not isinstance(payload, dict):
return "unavailable"
detail = payload.get("detail")
if not isinstance(detail, list):
return "unavailable"
safe = []
for item in detail[:5]:
if not isinstance(item, dict):
continue
# Only the Pydantic error *type* is safe to expose, not message/input/context.
kind = item.get("type")
if not isinstance(kind, str) or not re.fullmatch(r"[a-z][a-z0-9_]{0,63}", kind):
continue
loc = item.get("loc")
# Restrict locations to known schema tokens; never echo dynamic names.
known = {"body", "model", "state", "questions", "type", "instructions",
"choices", "levels", "value", "description", "label"}
path = ".".join(part for part in loc if isinstance(part, str) and part in known) if isinstance(loc, list) else ""
safe.append(kind + (":" + path if path else ""))
return ",".join(safe) or "unavailable"
def _diagnostics_enabled() -> bool:
"""Opt-in numeric decision diagnostics; never expose state, prompts or choice text."""
return os.getenv("JEV_DECISION_DIAGNOSTICS", "false").strip().lower() == "true"
def _validation_error(code: str) -> ValueError:
# Explicit, stable codes for logs; existing fallback/exception behavior stays unchanged.
return ValueError(code)
async def evaluate(point: str, messages: list, questions: list[dict], *, llm=None, operation=None, group=None) -> dict:
if not questions or len({q["name"] for q in questions}) != len(questions):
raise ValueError("Decision questions must have unique names")
if any(q["type"] == "choice" and not q["choices"] for q in questions):
raise ValueError("Decision choice requires options")
started = time.monotonic()
state = json.dumps({"policy_and_context": messages}, ensure_ascii=False, default=str)
# The default Vercel wire contract remains byte-for-byte equivalent.
direct_typesafe = urllib.parse.urlsplit(_jev_endpoint()).hostname == "api.typesafe.ai"
if direct_typesafe:
from .adapters.typesafe import build_request, normalize_answers
request_body = build_request(state, questions)
else:
request_body = {"model": os.getenv("JEV_MODEL", "typesafe-ai/jev"),
"input": state, "questions": questions}
response = await asyncio.to_thread(_post, request_body)
await _record(llm, point, response, operation)
answers = normalize_answers(response, questions) if direct_typesafe else response.get("answers")
if not isinstance(answers, list) or len(answers) != len(questions):
raise _validation_error("MISSING_DECISION_ANSWERS")
indexed = {a.get("name"): a for a in answers if isinstance(a, dict)}
if len(indexed) != len(questions):
raise _validation_error("DUPLICATE_OR_INVALID_ANSWERS")
result, confidences, predicate_probabilities = {}, [], []
diagnostic_questions = []
for q in questions:
a = indexed.get(q["name"], {})
if a.get("type") != q["type"]:
raise _validation_error("ANSWER_TYPE_MISMATCH")
if q["type"] == "choice":
value = a.get("choice")
if value not in {v["value"] for v in q["choices"]}:
raise _validation_error("UNDECLARED_CHOICE")
# Missing confidence must never be promoted to certainty.
confidences.append(_number(a.get("confidence", 0), 0, 1))
if _diagnostics_enabled():
options = [v["value"] for v in q["choices"]]
detail = {"question_index": len(diagnostic_questions), "type": "choice",
"selected_option_index": options.index(value),
"confidence": round(confidences[-1], 4)}
if isinstance(a.get("option_probabilities"), list):
detail["option_probabilities"] = [round(_number(v, 0, 1), 4) for v in a["option_probabilities"]]
diagnostic_questions.append(detail)
elif q["type"] == "predicate":
probability = _number(a.get("probability"), 0, 1)
value = probability >= 0.5
confidences.append(max(probability, 1-probability))
predicate_probabilities.append((q["name"], probability))
if _diagnostics_enabled():
diagnostic_questions.append({"question_index": len(diagnostic_questions), "type": "predicate",
"decision": value, "probability_true": round(probability, 4),
"confidence": round(confidences[-1], 4)})
else:
value = _number(a.get("score"), 0, len(q["levels"])-1)
confidences.append(_number(a.get("confidence", 0), 0, 1))
if _diagnostics_enabled():
diagnostic_questions.append({"question_index": len(diagnostic_questions), "type": "score",
"score": value, "confidence": round(confidences[-1], 4)})
result[q["name"]] = value
confidence = min(confidences)
default_minimum = os.getenv(env_key(group).replace("_PROVIDER", "_MIN_CONFIDENCE"), os.getenv("JEV_MIN_CONFIDENCE", "0.7")) if group else os.getenv("JEV_MIN_CONFIDENCE", "0.7")
minimum = float(os.getenv(env_key(point).replace("_PROVIDER", "_MIN_CONFIDENCE"), default_minimum))
_number(minimum, 0, 1)
if _diagnostics_enabled():
logger.info("jev.decision.diagnostics point=%s status=%s confidence=%.4f minimum_confidence=%.4f questions=%s",
point, "accepted" if confidence >= minimum else "rejected_low_confidence",
confidence, minimum, json.dumps(diagnostic_questions, separators=(",", ":")))
if confidence < minimum:
# A NOUL value is P(True), not confidence in the True answer.
# Log only numeric calibration data, never decision input or question text.
logger.warning("decision point=%s provider=jev confidence=%.4f minimum_confidence=%.4f status=rejected predicate_probabilities=%s",
point, confidence, minimum,
[(name, round(probability, 4)) for name, probability in predicate_probabilities])
raise _validation_error("CONFIDENCE_BELOW_THRESHOLD")
result["confidence"] = confidence
result["reason"] = "JEV typed decision (no generated explanation)."
logger.info("decision point=%s provider=jev model=%s elapsed_ms=%.1f usage=%s gateway=%s",
point, response.get("model"), (time.monotonic()-started)*1000,
response.get("usage"), response.get("provider_metadata", {}).get("gateway", {}))
return result
async def invoke(llm: Any, messages: list, *, decision_point: str, decision_questions: list,
decision_render=None, decision_generative: bool = False, decision_group: str | None = None, **kwargs: Any) -> str:
if decision_generative or not selected(decision_point, decision_group):
return await llm.ainvoke(messages, **kwargs)
try:
result = await evaluate(decision_point, messages, decision_questions, llm=llm, operation=kwargs.get("generation_name"), group=decision_group)
return decision_render(result) if decision_render else json.dumps(result, ensure_ascii=False)
except Exception as exc:
# Do not log exception text, input, response bodies or credentials.
from urllib.error import HTTPError
from .adapters.typesafe import TypeSafeContractError
category = (exc.code if isinstance(exc, TypeSafeContractError) else
"HTTP_ERROR" if isinstance(exc, HTTPError) else
"VALIDATION_ERROR" if isinstance(exc, ValueError) else
"TRANSPORT_ERROR")
http_status = exc.code if isinstance(exc, HTTPError) else None
error_code = (exc.code if isinstance(exc, TypeSafeContractError) else
str(exc) if isinstance(exc, ValueError) and
str(exc) in {"MISSING_DECISION_ANSWERS", "DUPLICATE_OR_INVALID_ANSWERS",
"ANSWER_TYPE_MISMATCH", "UNDECLARED_CHOICE",
"CONFIDENCE_BELOW_THRESHOLD", "INVALID_PROBABILITY_OR_SCORE"}
else "UNCLASSIFIED")
http_detail = _safe_http_diagnostic(exc) if isinstance(exc, HTTPError) else "not_applicable"
logger.warning("decision point=%s provider=jev failure=%s category=%s http_status=%s "
"error_code=%s http_schema=%s question_types=%s",
decision_point, type(exc).__name__, category, http_status,
error_code, http_detail, ",".join(sorted({q.get("type", "unknown") for q in decision_questions if isinstance(q, dict)})))
fallback = os.getenv("DECISION_FALLBACK_TO_CURRENT", "true").strip().lower()
if fallback not in {"true", "false"}:
raise ValueError("DECISION_FALLBACK_TO_CURRENT must be true or false")
if fallback != "true":
raise
logger.info("decision point=%s provider=current fallback=true", decision_point)
return await llm.ainvoke(messages, **kwargs)
async def multi_intent(llm, messages, *, intents, text, **kwargs):
"""Keep source-fragment extraction and request order; JEV selects each intent."""
raw = await llm.ainvoke(messages, **kwargs)
if not selected("multi_intent"):
return raw
from agent_framework.llm.structured_output import parse_json_object
candidates = parse_json_object(raw).get("operations")
if not isinstance(candidates, list) or not candidates:
raise ValueError("Multi-intent extraction returned no candidates")
if any(not isinstance(c, dict) or not str(c.get("source_text") or "").strip()
or str(c["source_text"]) not in text for c in candidates):
raise ValueError("Multi-intent source must be a literal user-message fragment")
questions = [choice("operation_" + str(n), [i.name for i in intents] + ["__unsupported__"],
"Classify ONLY this extracted objective: " + c["source_text"] + ". Use __unsupported__ for an objective outside the intent catalog.") for n, c in enumerate(candidates)]
class ExtractedProvider:
usage_repository = getattr(llm, "usage_repository", None)
telemetry = getattr(llm, "telemetry", None)
async def ainvoke(self, messages, **kwargs):
return raw
return await invoke(ExtractedProvider(), messages, decision_point="multi_intent", decision_questions=questions,
decision_render=lambda d: json.dumps({"confidence": d["confidence"], "operations": [
{"intent": None if d["operation_" + str(n)] == "__unsupported__" else d["operation_" + str(n)],
"unsupported": d["operation_" + str(n)] == "__unsupported__", "source_text": c["source_text"]}
for n, c in enumerate(candidates)]}))
async def _record(llm, point, response, operation):
"""Account for billed responses even if validation later rejects them."""
if llm is None:
return
usage = response.get("usage") or {}
gateway = (response.get("provider_metadata") or {}).get("gateway") or {}
model = response.get("model") or os.getenv("JEV_MODEL", "typesafe-ai/jev")
metadata = {"provider": "jev", "model": model, "decision_point": point,
"generation_id": gateway.get("generationId"), "gateway_cost": gateway.get("cost")}
try:
repository = getattr(llm, "usage_repository", None)
if repository is not None:
from agent_framework.billing.usage_repository import UsageRecord
await repository.record(UsageRecord.from_usage("jev", model, operation or "decision." + point,
{"prompt_tokens": usage.get("input_tokens", 0), "completion_tokens": usage.get("output_tokens", 0),
"total_tokens": usage.get("total_tokens", 0), "cost_usd": gateway.get("cost", 0)}, metadata))
telemetry = getattr(llm, "telemetry", None)
if telemetry is not None:
await telemetry.event("decision.jev", {**metadata, "usage": usage})
except Exception as exc:
logger.warning("decision accounting point=%s failure=%s", point, type(exc).__name__)

View File

@@ -1,9 +1,5 @@
from __future__ import annotations from __future__ import annotations
from agent_framework.decisions import invoke as decision_invoke, choice
from agent_framework.localization import terms
from agent_framework.llm.structured_output import parse_json_object from agent_framework.llm.structured_output import parse_json_object
import json import json
@@ -17,7 +13,9 @@ from .session_store import InMemoryGlobalSessionStore
logger = logging.getLogger("agent_framework.global_supervisor") logger = logging.getLogger("agent_framework.global_supervisor")
# Terminal vocabulary is read from the selected locale at request time. _TERMINAL_WORDS = {
"obrigado", "obrigada", "valeu", "tchau", "encerrar", "fim", "cancelar atendimento"
}
class GlobalSupervisorRouter: class GlobalSupervisorRouter:
@@ -164,12 +162,10 @@ class GlobalSupervisorRouter:
return fallback or self._route_by_rules(text, mode) return fallback or self._route_by_rules(text, mode)
prompt = self._build_supervisor_prompt(text, request) prompt = self._build_supervisor_prompt(text, request)
try: try:
raw = await decision_invoke(self.llm, [ raw = await self.llm.ainvoke([
{"role": "system", "content": "Você é um supervisor global de backends. Responda somente JSON válido."}, {"role": "system", "content": "Você é um supervisor global de backends. Responda somente JSON válido."},
{"role": "user", "content": prompt}, {"role": "user", "content": prompt},
], temperature=0, decision_point='global_supervisor', ], temperature=0, profile_name="supervisor", component_name="supervisor", generation_name="llm.supervisor")
decision_questions=[choice("backend_id", list(self.registry.backends))],
profile_name="supervisor", component_name="supervisor", generation_name="llm.supervisor")
data = self._parse_json(raw) data = self._parse_json(raw)
backend_id = str(data.get("backend") or data.get("backend_id") or "").strip() backend_id = str(data.get("backend") or data.get("backend_id") or "").strip()
if backend_id not in self.registry.backends: if backend_id not in self.registry.backends:
@@ -217,10 +213,10 @@ class GlobalSupervisorRouter:
n = self._normalize(text) n = self._normalize(text)
if not n: if not n:
return True return True
if n in {self._normalize(value) for value in terms("global_router.terminal")}: if n in _TERMINAL_WORDS:
return False return False
tokens = n.split() tokens = n.split()
followup_markers = [self._normalize(value) for value in terms("global_router.followup")] followup_markers = ["esse", "essa", "isso", "valor", "ele", "ela", "tambem", "e ", "entao", "nesse", "nessa"]
return len(tokens) <= 6 or any(marker in n for marker in followup_markers) return len(tokens) <= 6 or any(marker in n for marker in followup_markers)
def _extract_text(self, request: GlobalRouteRequest) -> str: def _extract_text(self, request: GlobalRouteRequest) -> str:

View File

@@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from agent_framework.decisions import selected
import json import json
import os import os
from typing import Any from typing import Any
@@ -22,24 +20,59 @@ from .prompts.pinj import build_pinj_prompt
from .prompts.ragsec import build_ragsec_prompt from .prompts.ragsec import build_ragsec_prompt
from .prompts.fallback import build_fallback_prompt from .prompts.fallback import build_fallback_prompt
# Vocabulary is owned by framework_llm_client and the locale catalog. _AOFERTA_TRIGGERS = (
"quer aproveitar",
"que tal tambem",
"que tal também",
"posso ja",
"posso já",
"ja que esta",
"já que está",
"aproveita e",
"aproveite e",
"tambem cancelar",
"também cancelar",
)
# Mock determinístico do REVPREC: substrings de ação dada como FEITA (a pergunta do rail # Mock determinístico do REVPREC: substrings de ação dada como FEITA (a pergunta do rail
# desde 2026-08-06). A detecção rica (fatura × ação, protocolo, histórico) é do prompt. # desde 2026-08-06). A detecção rica (fatura × ação, protocolo, histórico) é do prompt.
# Vocabulary is owned by framework_llm_client and the locale catalog. _REVPREC_MARKERS = (
"cancelamento confirmado",
"foi cancelado",
"cancelado com sucesso",
"cancelei",
"cancelamos",
"retiramos o valor",
"retirei o valor",
"contestacao foi registrada",
"contestação foi registrada",
)
# Vocabulary is owned by framework_llm_client and the locale catalog. _TOXOUT_MOCK_PATTERNS = (
r"\b(idiota|imbecil|burro|estúpido|inútil|maldito|miserável|incompetente)\b",
r"\b(idiots?|stupid|useless|moron)\b",
)
# Vocabulary is owned by framework_llm_client and the locale catalog. _OOS_MOCK_TRIGGERS = (
"política",
"religião",
"presidente",
"concorrente",
"vivo",
)
# Substrings inequívocas de fraseado proibido (mock determinístico). Mantidas # Substrings inequívocas de fraseado proibido (mock determinístico). Mantidas
# curtas e sem ambiguidade para não colidir com falas legítimas; a detecção rica # curtas e sem ambiguidade para não colidir com falas legítimas; a detecção rica
# (allow-list, "entendo" no início etc.) é responsabilidade do prompt 20b real. # (allow-list, "entendo" no início etc.) é responsabilidade do prompt 20b real.
# Vocabulary is owned by framework_llm_client and the locale catalog. _FRASEOLOGIA_MOCK_TRIGGERS = (
"bundle",
"parceiro",
"terceiros",
)
# Tasks cujo prompt pede UM DÍGITO (1 = passa, 0 = bloqueia) em vez de JSON, com o # Tasks cujo prompt pede UM DÍGITO (1 = passa, 0 = bloqueia) em vez de JSON, com o
@@ -154,7 +187,7 @@ class GuardrailLLMClient:
injete o `LangfuseCallbackHandler` para que o `ChatLLM` da reescrita injete o `LangfuseCallbackHandler` para que o `ChatLLM` da reescrita
apareca como span no Langfuse. apareca como span no Langfuse.
""" """
if self.use_mock and not selected("guardrail_" + task.lower()): if self.use_mock:
return self._mock_classify(task, payload) return self._mock_classify(task, payload)
# O caminho real usa exclusivamente o provider oficial do framework. # O caminho real usa exclusivamente o provider oficial do framework.

View File

@@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from agent_framework.localization import message, protocol_pattern, vocalize_protocol
import re import re
from ._compat import RailResult, span from ._compat import RailResult, span
@@ -50,12 +48,26 @@ def ausencia_oferta_proativa(text: str, context: dict = None, *, callbacks: list
) )
# Protocol vocabulary comes from the selected locale catalog. _DIGIT_WORDS_RE = (
r"(?:zero|um|dois|tr[êe]s|quatro|cinco|seis|sete|oito|nove)"
)
# Token vocalizado: palavra de dígito ou letra única (a-z). # Token vocalizado: palavra de dígito ou letra única (a-z).
# Protocol vocabulary comes from the selected locale catalog. _SPOKEN_TOKEN_RE = rf"(?:{_DIGIT_WORDS_RE}|[a-z])"
# 6+ tokens vocalizados separados por espaço (cobre PRT-XXXX vocalizado). # 6+ tokens vocalizados separados por espaço (cobre PRT-XXXX vocalizado).
# Protocol vocabulary comes from the selected locale catalog. _SPOKEN_PROTOCOL_RE = (
# Protocol vocabulary comes from the selected locale catalog. rf"(?:{_SPOKEN_TOKEN_RE}\s+){{5,}}{_SPOKEN_TOKEN_RE}\b"
)
_PROTOCOL_PATTERN = re.compile(
r"(?i)\bprotocolo\b"
r"[\s\S]{0,40}?"
r"(?:"
r"\d{6,}" # formato legado: 6+ dígitos literais
r"|"
r"PRT-[A-Z0-9]{6,}" # formato bruto da provedor (caso o LLM não vocalize)
r"|"
rf"{_SPOKEN_PROTOCOL_RE}" # formato vocalizado (palavras + letras)
r")"
)
def compliance_anatel(text: str, context: dict) -> RailResult: def compliance_anatel(text: str, context: dict) -> RailResult:
@@ -84,7 +96,7 @@ def compliance_anatel(text: str, context: dict) -> RailResult:
mechanism="regex", mechanism="regex",
) )
expected = list(context.get("expected_protocols") or []) expected = list(context.get("expected_protocols") or [])
has_protocol = bool(protocol_pattern().search(text)) has_protocol = bool(_PROTOCOL_PATTERN.search(text))
if not has_protocol: if not has_protocol:
return RailResult( return RailResult(
allowed=False, allowed=False,

View File

@@ -22,8 +22,6 @@ Ambos retornam `RailResult.allowed=True`; o caller substitui o texto por
""" """
from __future__ import annotations from __future__ import annotations
from agent_framework.localization import message
import logging import logging
import re import re
@@ -89,7 +87,9 @@ def _mask_card(match: "re.Match") -> str:
return _CARD_MASK if _looks_like_card(digits) else digits return _CARD_MASK if _looks_like_card(digits) else digits
# Canonical toxicity response is localized at call time. _TOXOUT_CANONICAL_MESSAGE = (
"Não consegui formular uma resposta adequada, posso ajudar de outra forma?"
)
_client = GuardrailLLMClient() _client = GuardrailLLMClient()
@@ -228,7 +228,7 @@ def _sanitize_toxic(
with span("rail.TOXOUT.canonical", mechanism="python"): with span("rail.TOXOUT.canonical", mechanism="python"):
logger.warning("guardrails.toxic_fallback_canonical") logger.warning("guardrails.toxic_fallback_canonical")
return message("toxicity.canonical"), "canonical" return _TOXOUT_CANONICAL_MESSAGE, "canonical"
def mascarar_pii_output(text: str, context: dict = None) -> RailResult: def mascarar_pii_output(text: str, context: dict = None) -> RailResult:

View File

@@ -19,8 +19,6 @@ do agente.
""" """
from __future__ import annotations from __future__ import annotations
from agent_framework.localization import CatalogMapping
import logging import logging
import os import os
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
@@ -44,7 +42,64 @@ _client = GuardrailLLMClient()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# 2026-05-16 # 2026-05-16
_FALLBACK_BY_CODE = CatalogMapping("messages.fallback") _FALLBACK_BY_CODE: dict[str, str] = {
"INPUT_SIZE": (
"Sua mensagem ficou muito longa pra eu processar de uma vez. "
"Pode reformular de forma mais curta ou dividir em partes menores "
"e me reenviar?"
),
"AOFERTA": (
"Posso te ajudar com mais alguma dúvida sobre sua conta ou fatura?"
),
"REVPREC": (
"No momento não consigo confirmar essa ação dessa forma. "
"Vou continuar verificando as informações disponíveis."
),
"CMP": (
"Não consegui validar todas as informações necessárias neste momento. "
"Vou seguir verificando os dados do atendimento."
),
"OOS": (
"Essa solicitação está fora do meu escopo de atendimento. "
"Posso te ajudar com dúvidas sobre contas, consumo ou faturas da provedor."
),
"DLEX_IN": (
"Não consegui interpretar essa solicitação com segurança. "
"Pode reformular sua mensagem de outra forma?"
),
"PINJ": (
"Não consegui processar essa solicitação da forma enviada. "
"Pode reformular sua pergunta para continuarmos?"
),
"RAGSEC": (
"Não encontrei informações suficientes para responder isso com segurança. "
"Pode detalhar melhor sua solicitação?"
),
"DLEX_OUT": (
"Prefiro reformular minha resposta para evitar informações incorretas. "
"Pode me confirmar exatamente o que deseja consultar?"
),
"TOX": (
"Entendo que essa situação é frustrante. Vou te ajudar a verificar isso."
),
"INTENCAO_CANCELAR": (
"Deixa eu confirmar o que você gostaria de fazer: você quer entender "
"o que é essa cobrança ou prefere cancelar o serviço?"
),
"CORRESPONDENCIA_ITEM": (
"Preciso confirmar um detalhe antes de prosseguirmos. Pode me confirmar "
"qual serviço você deseja cancelar e o valor que esperava?"
),
"ALCADA": (
"Este ajuste precisa ser analisado por um especialista provedor. "
"Vou encaminhar seu atendimento para continuar com um especialista "
"que poderá te ajudar melhor nesse caso."
),
"ACTION_CONFIRMATION_RETRY": (
"Antes de prosseguirmos, preciso confirmar: você gostaria mesmo de "
"realizar essa ação?"
),
}
#2026-05-19 #2026-05-19
def _run_rail( def _run_rail(

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