mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-11-06 11:24:13 +00:00
Compare commits
70 Commits
e517f5e6a3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 01b456fdc5 | |||
| f035580c21 | |||
| ddd9d9a0d4 | |||
|
|
0d986e025a | ||
|
|
ccf509dd21 | ||
| 4ca6c8a274 | |||
| 6a511b9005 | |||
| 1916e49c45 | |||
| b2d5cc09b2 | |||
| e3bd48cf4b | |||
|
|
1942f78df8 | ||
|
|
b4a1a43eee | ||
|
|
6c5fb33c08 | ||
| 20a891b1d7 | |||
| 42e1720d1c | |||
| 5149fb9a5e | |||
| a03c6fd325 | |||
| d986b9751f | |||
| 50e7db4666 | |||
| 63810ab5c7 | |||
| edf46f9429 | |||
| 6f859a991a | |||
| db16d478ba | |||
| 3cb970d8ab | |||
| b7d68317ca | |||
| a2fe158d62 | |||
| e6bc243cf7 | |||
| c9eb71af32 | |||
| ab0aa5bc11 | |||
| a2c7a946c8 | |||
| a5f24dac3f | |||
| d535ec2a16 | |||
| f97714ac40 | |||
| d5fd41a40c | |||
| dd40662776 | |||
| cd07ad079c | |||
| 39ff42806c | |||
| a9bb79d59a | |||
|
|
2e5ec5cace | ||
|
|
b24df5abdc | ||
| ca2dceda4e | |||
| ea6d278ef8 | |||
| 75e8bec89c | |||
| 04493c3364 | |||
| 7a749cb93b | |||
| 535f7c0e1a | |||
| e8c7b591a8 | |||
| c120b96bf8 | |||
|
|
2a9d9001b0 | ||
| 5a06eeef24 | |||
| d5a671cf57 | |||
|
|
279489efb4 | ||
|
|
4717ebedb3 | ||
| 330b43c36f | |||
| c2e511f6ec | |||
| b0f5633132 | |||
| f43cc65809 | |||
| d4d16889f7 | |||
| 479ceb9b7a | |||
| 9402e1d70c | |||
| a30ca76433 | |||
| fde9465a0e | |||
| 66a67cb503 | |||
| adfe585807 | |||
| 84513764cf | |||
| f128d8870c | |||
| 5de39f274d | |||
| e7bba0ec0b | |||
| 59edc85b26 | |||
| a9e4fa042a |
56
.env.jev.example
Normal file
56
.env.jev.example
Normal 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
3
.idea/workspace.xml
generated
@@ -179,7 +179,8 @@
|
|||||||
<workItem from="1785453511270" duration="1006000" />
|
<workItem from="1785453511270" duration="1006000" />
|
||||||
<workItem from="1792665278602" duration="734000" />
|
<workItem from="1792665278602" duration="734000" />
|
||||||
<workItem from="1793193595355" duration="436000" />
|
<workItem from="1793193595355" duration="436000" />
|
||||||
<workItem from="1793194079956" duration="4230000" />
|
<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" />
|
||||||
|
|||||||
84
JEV_INTEGRATION.md
Normal file
84
JEV_INTEGRATION.md
Normal 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.
|
||||||
42
README.md
42
README.md
@@ -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
|
## 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/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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -24,3 +24,5 @@ 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"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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
|
||||||
@@ -10,6 +11,10 @@ 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
|
||||||
@@ -39,6 +44,10 @@ 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
|
||||||
@@ -138,6 +147,8 @@ 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
|
||||||
@@ -174,6 +185,7 @@ 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
|
||||||
|
|||||||
@@ -19,3 +19,6 @@ 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
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
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
|
||||||
|
|
||||||
@@ -55,8 +57,11 @@ 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 llm.ainvoke(
|
response = await decision_invoke(llm,
|
||||||
[{"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,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,2 @@
|
|||||||
|
"""Opt-in typed decision providers. Generative tasks keep their existing provider."""
|
||||||
|
from .client import choice, predicate, score, invoke, selected, evaluate
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
|||||||
|
"""Optional decision-provider adapters; no changes to the default engine."""
|
||||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||||
298
libs/agent_framework/src/agent_framework/decisions/client.py
Normal file
298
libs/agent_framework/src/agent_framework/decisions/client.py
Normal 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__)
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
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.localization import terms
|
||||||
|
|
||||||
from agent_framework.llm.structured_output import parse_json_object
|
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)
|
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 self.llm.ainvoke([
|
raw = await decision_invoke(self.llm, [
|
||||||
{"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, 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)
|
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:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,7 @@
|
|||||||
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
|
||||||
@@ -152,7 +154,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:
|
if self.use_mock and not selected("guardrail_" + task.lower()):
|
||||||
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.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user