Compare commits

..

70 Commits

Author SHA1 Message Date
01b456fdc5 new feature: JEV Integration. Now there are a current (native) LLM decision model and a JEV decision model 2026-11-05 07:51:00 -03:00
f035580c21 fix: Spec Agent Runtime 2026-10-30 10:39:34 -03:00
ddd9d9a0d4 fix: Spec Agent Runtime 2026-10-30 10:22:53 -03:00
T3782834
0d986e025a adding DB Error in Observability 2026-10-28 13:05:53 -03:00
T3782834
ccf509dd21 new feature: LOCALE pt-BR/en 2026-10-27 11:45:37 -03:00
4ca6c8a274 docs for A2A english 2026-10-22 15:21:09 -03:00
6a511b9005 docs for A2A english 2026-10-22 08:19:54 -03:00
1916e49c45 Adjustments for /oracle organization 2026-10-21 09:47:31 -03:00
b2d5cc09b2 Adjustments for /oracle organization 2026-10-21 08:38:42 -03:00
e3bd48cf4b A2A Protocol 2026-10-21 08:23:55 -03:00
T3782834
1942f78df8 bugfix: rollback of handoff 2026-09-22 23:42:40 -03:00
T3782834
b4a1a43eee bugfix: handoff 2026-09-22 21:36:07 -03:00
T3782834
6c5fb33c08 bugfix: handoff 2026-09-22 21:27:37 -03:00
20a891b1d7 bugfix: max_tokens hardcoded 2026-09-19 00:30:51 -03:00
42e1720d1c Multi-item documentation 2026-09-18 23:35:03 -03:00
5149fb9a5e bugfix: Multi-item 2026-09-18 23:11:58 -03:00
a03c6fd325 new feature: transaction interruption for same intent replacement 2026-09-18 11:47:29 -03:00
d986b9751f bugfix: ABANDON precedence 2026-09-17 10:23:35 -03:00
50e7db4666 adding ABANDON in multi-intent/intent-shift 2026-09-17 00:06:00 -03:00
63810ab5c7 adjustment: extra attributes for OTEL 2026-09-16 20:36:55 -03:00
edf46f9429 manual agent_template_backend 2026-09-16 08:57:20 -03:00
6f859a991a adjustments: manual v4 2026-09-13 08:52:57 -03:00
db16d478ba adjustments: manual v3 2026-09-12 15:33:55 -03:00
3cb970d8ab adjustments: manual v2 2026-09-12 08:04:25 -03:00
b7d68317ca adjustments: manual 2026-09-12 07:11:07 -03:00
a2fe158d62 new feature: frontend with separated responses 2026-09-11 11:59:46 -03:00
e6bc243cf7 new feature: frontend with separated responses 2026-09-11 10:45:49 -03:00
c9eb71af32 new feature: frontend with separated responses 2026-09-11 09:33:50 -03:00
ab0aa5bc11 new feature: Multi-Intent 2026-09-11 00:34:56 -03:00
a2c7a946c8 new feature: Multi-Intent 2026-09-10 22:55:50 -03:00
a5f24dac3f new feature: Multi-Intent 2026-09-10 16:12:04 -03:00
d535ec2a16 ajustes no manual 2026-09-10 09:48:25 -03:00
f97714ac40 adjustment: load-tester 2026-09-09 13:51:05 -03:00
d5fd41a40c bugfix: extractor cases 8 and 16 2026-09-08 10:13:03 -03:00
dd40662776 bugfix reconciliation extractor 2026-09-07 22:57:36 -03:00
cd07ad079c bugfix reconciliation extractor 2026-09-07 20:16:57 -03:00
39ff42806c nova funcionalidade: reconciliacao temporal 2026-09-02 13:04:56 -03:00
a9bb79d59a Ajustes no template para definicao de campos para extracao 2026-09-01 20:58:51 -03:00
T3782834
2e5ec5cace ajuste no contas 2026-09-01 11:23:22 -03:00
T3782834
b24df5abdc ajuste no guardraild COE 2026-08-31 21:11:51 -03:00
ca2dceda4e Conferencia de parametros coletados na transacao 2026-08-31 17:27:35 -03:00
ea6d278ef8 Ajustes conforme relatorio de testes 2026-08-27 2026-08-29 21:04:31 -03:00
75e8bec89c Ajustes conforme relatorio de testes 2026-08-27 2026-08-29 11:22:10 -03:00
04493c3364 Ajustes conforme relatorio de testes 2026-08-27 2026-08-29 10:03:42 -03:00
7a749cb93b Documentation organization 2026-08-27 21:11:01 -03:00
535f7c0e1a bugfix: oci_openai provider 2026-08-27 20:30:27 -03:00
e8c7b591a8 Documentation organization 2026-08-27 10:28:23 -03:00
c120b96bf8 Documentation organization 2026-08-27 09:41:46 -03:00
T3782834
2a9d9001b0 new feature: Integration with kbdb Autonomous 2026-08-26 10:15:51 -03:00
5a06eeef24 new feature: External guardrails/judges 2026-08-24 11:55:07 -03:00
d5a671cf57 new feature: External guardrails/judges 2026-08-24 11:26:08 -03:00
T3782834
279489efb4 adjustments: transaction parameter extraction 2026-08-21 23:06:54 -03:00
T3782834
4717ebedb3 adjustments: transaction parameter extraction 2026-08-21 22:44:36 -03:00
330b43c36f Adding transaction state 2026-08-21 17:59:06 -03:00
c2e511f6ec Adding transaction state 2026-08-21 16:47:13 -03:00
b0f5633132 bugfixes: router stickness vs transaction workflow parameters 2026-08-21 14:34:30 -03:00
f43cc65809 bugfix: enviar subject para contexto do guardrail 2026-08-21 11:01:48 -03:00
d4d16889f7 bugfix: aofertas 2026-08-21 10:47:19 -03:00
479ceb9b7a new feature: reasoning_content for compatible models in ainvoke_response() 2026-08-21 07:40:21 -03:00
9402e1d70c bugfixes: router stickness vs transaction workflow parameters 2026-08-20 23:29:56 -03:00
a30ca76433 bugfixes: router stickness vs transaction workflow parameters 2026-08-20 22:58:25 -03:00
fde9465a0e new feature: mcp pre-validation 2026-08-20 15:13:20 -03:00
66a67cb503 adjustments: route stickness usefull tokens only 2026-08-20 13:14:57 -03:00
adfe585807 adjustments: route stickness usefull tokens only 2026-08-20 11:44:24 -03:00
84513764cf new feature: transaction evidence 2026-08-20 09:52:22 -03:00
f128d8870c bugfix: route stickness precedences (transaction in the same intent) 2026-08-20 09:18:58 -03:00
5de39f274d bugfixes: transaction parameter collector, generic formatting messages, guardrails, prompts. Testing contas 2026-08-20 00:23:50 -03:00
e7bba0ec0b Melhorias no guardrails de FRASEOLOGY. Melhorias no Transaction Workflow 2026-08-19 16:08:03 -03:00
59edc85b26 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
a9e4fa042a Testes de Carga: /deploy/oke/load-test 2026-08-17 18:59:14 -03:00
235 changed files with 6158 additions and 409 deletions

56
.env.jev.example Normal file
View File

@@ -0,0 +1,56 @@
# 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

3
.idea/workspace.xml generated
View File

@@ -179,7 +179,8 @@
<workItem from="1785453511270" duration="1006000" />
<workItem from="1792665278602" duration="734000" />
<workItem from="1793193595355" duration="436000" />
<workItem from="1793194079956" duration="4230000" />
<workItem from="1793194079956" duration="5443000" />
<workItem from="1793271962068" duration="758000" />
</task>
<task id="LOCAL-00001" summary="Ajustes na documentação e remanejamento dos folders">
<option name="closed" value="true" />

84
JEV_INTEGRATION.md Normal file
View File

@@ -0,0 +1,84 @@
# 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

@@ -49,89 +49,91 @@ 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
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**
### [SPEC-001 — Architecture](specs/SPEC-001-Architecture.md)
### [SPEC-001 — Architecture](specs/en/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.
### [SPEC-002 — Agent Runtime](specs/SPEC-002-Agent-Runtime.md)
### [SPEC-002 — Agent Runtime](specs/en/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.
### [SPEC-003 — Agent Gateway](specs/SPEC-003-Agent-Gateway.md)
### [SPEC-003 — Agent Gateway](specs/en/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.
### [SPEC-004 — MCP Gateway](specs/SPEC-004-MCP-Gateway.md)
### [SPEC-004 — MCP Gateway](specs/en/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.
### [SPEC-005 — Guardrails](specs/SPEC-005-Guardrails.md)
### [SPEC-005 — Guardrails](specs/en/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.
### [SPEC-006 — Evals](specs/SPEC-006-Evals.md)
### [SPEC-006 — Evals](specs/en/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.
### [SPEC-007 — Observability](specs/SPEC-007-Observability.md)
### [SPEC-007 — Observability](specs/en/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.
### [SPEC-008 — Deployment](specs/SPEC-008-Deployment.md)
### [SPEC-008 — Deployment](specs/en/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.
### [SPEC-009 — Channel Gateway](specs/SPEC-009-Channel-Gateway.md)
### [SPEC-009 — Channel Gateway](specs/en/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.
### [SPEC-010 — Agent Development](specs/SPEC-010-Agent-Development.md)
### [SPEC-010 — Agent Development](specs/en/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.
### [SPEC-011 — Governance Model](specs/SPEC-011-Governance-Model.md)
### [SPEC-011 — Governance Model](specs/en/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.
### [SPEC-012 — Canonical Contracts](specs/SPEC-012-Canonical-Contracts.md)
### [SPEC-012 — Canonical Contracts](specs/en/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.
### [SPEC-013 — Versioning and Compatibility Model](specs/SPEC-013-Versioning-and-Compatibility-Model.md)
### [SPEC-013 — Versioning and Compatibility Model](specs/en/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.
### [SPEC-014 — Templates and Agent Creation Model](specs/SPEC-014-Templates-and-Agent-Creation-Model.md)
### [SPEC-014 — Templates and Agent Creation Model](specs/en/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.
### [SPEC-015 — Adoption and Eligibility Criteria](specs/SPEC-015-Adoption-and-Eligibility-Criteria.md)
### [SPEC-015 — Adoption and Eligibility Criteria](specs/en/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.
### [SPEC-016 — Agent Development Lifecycle](specs/SPEC-016-Agent-Development-Lifecycle.md)
### [SPEC-016 — Agent Development Lifecycle](specs/en/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.
### [SPEC-017 — Release Management and CI/CD](specs/SPEC-017-Release-Management-and-CICD.md)
### [SPEC-017 — Release Management and CI/CD](specs/en/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.
### [SPEC-018 — Security and Identity Model](specs/SPEC-018-Security-and-Identity-Model.md)
### [SPEC-018 — Security and Identity Model](specs/en/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.
### [SPEC-019 — Evaluation and Certification Framework](specs/SPEC-019-Evaluation-and-Certification-Framework.md)
### [SPEC-019 — Evaluation and Certification Framework](specs/en/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.
### [SPEC-020 — Operational Readiness and SRE Model](specs/SPEC-020-Operational-Readiness-and-SRE-Model.md)
### [SPEC-020 — Operational Readiness and SRE Model](specs/en/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.

View File

@@ -1,159 +0,0 @@
# 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

@@ -24,3 +24,5 @@ Requires-Dist: motor>=3.6.0
Requires-Dist: google-cloud-pubsub>=2.28.0
Requires-Dist: mcp>=1.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,6 +3,7 @@ src/agent_framework/__init__.py
src/agent_framework/extensions.py
src/agent_framework/gateway_policy_context.py
src/agent_framework/idempotency.py
src/agent_framework/localization.py
src/agent_framework/observer.py
src/agent_framework/runtime_mcp_gateway_adapter.py
src/agent_framework.egg-info/PKG-INFO
@@ -10,6 +11,10 @@ src/agent_framework.egg-info/SOURCES.txt
src/agent_framework.egg-info/dependency_links.txt
src/agent_framework.egg-info/requires.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/composite_publisher.py
src/agent_framework/analytics/event_builder.py
@@ -39,6 +44,10 @@ src/agent_framework/config/__init__.py
src/agent_framework/config/agent_registry.py
src/agent_framework/config/observability_mapping.yaml
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/oci_streaming.py
src/agent_framework/gateways/__init__.py
@@ -138,6 +147,8 @@ src/agent_framework/llm/profile_resolver.py
src/agent_framework/llm/providers.py
src/agent_framework/llm/structured_output.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/client.py
src/agent_framework/mcp/models.py
@@ -174,6 +185,7 @@ src/agent_framework/observability/noc_events.py
src/agent_framework/observability/noc_otel.py
src/agent_framework/observability/observer.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_exporter.py
src/agent_framework/observability/telemetry.py

View File

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

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
from agent_framework.decisions import invoke as decision_invoke, predicate
from dataclasses import dataclass
from typing import Any
@@ -55,8 +57,11 @@ async def classify_processing_interruption(
f"Complemento/interrupção atual: {supplement_client}\n"
)
try:
response = await llm.ainvoke(
response = await decision_invoke(llm,
[{"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,
fallback_max_tokens=8,
profile_name=profile_name,

View File

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

View File

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

View File

@@ -0,0 +1,97 @@
"""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

@@ -0,0 +1,298 @@
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,5 +1,7 @@
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
@@ -162,10 +164,12 @@ class GlobalSupervisorRouter:
return fallback or self._route_by_rules(text, mode)
prompt = self._build_supervisor_prompt(text, request)
try:
raw = await self.llm.ainvoke([
raw = await decision_invoke(self.llm, [
{"role": "system", "content": "Você é um supervisor global de backends. Responda somente JSON válido."},
{"role": "user", "content": prompt},
], temperature=0, profile_name="supervisor", component_name="supervisor", generation_name="llm.supervisor")
], temperature=0, decision_point='global_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)
backend_id = str(data.get("backend") or data.get("backend_id") or "").strip()
if backend_id not in self.registry.backends:

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
from agent_framework.decisions import selected
import json
import os
from typing import Any
@@ -152,7 +154,7 @@ class GuardrailLLMClient:
injete o `LangfuseCallbackHandler` para que o `ChatLLM` da reescrita
apareca como span no Langfuse.
"""
if self.use_mock:
if self.use_mock and not selected("guardrail_" + task.lower()):
return self._mock_classify(task, payload)
# O caminho real usa exclusivamente o provider oficial do framework.

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