mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-07-09 14:04:19 +00:00
First commit
This commit is contained in:
122
libs/agent_framework/docs/CONVERSATION_SUMMARY_MEMORY.md
Normal file
122
libs/agent_framework/docs/CONVERSATION_SUMMARY_MEMORY.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# ConversationSummaryMemory
|
||||
|
||||
Este módulo adiciona compressão de contexto conversacional ao framework sem substituir a memória bruta existente.
|
||||
|
||||
## Objetivo
|
||||
|
||||
O framework passa a trabalhar com dois níveis de memória:
|
||||
|
||||
1. **Histórico bruto**: mensagens completas persistidas por `ConversationMemory`.
|
||||
2. **Resumo incremental**: contexto antigo compactado por `ConversationSummaryMemory`.
|
||||
|
||||
O prompt final do agente pode receber:
|
||||
|
||||
```text
|
||||
Resumo da conversa até agora:
|
||||
{summary}
|
||||
|
||||
Últimas mensagens completas da conversa:
|
||||
{recent_messages}
|
||||
|
||||
Mensagem do usuário:
|
||||
{current_user_message}
|
||||
```
|
||||
|
||||
## Configuração
|
||||
|
||||
```env
|
||||
ENABLE_CONVERSATION_SUMMARY_MEMORY=true
|
||||
MEMORY_CONTEXT_STRATEGY=summary
|
||||
MEMORY_HISTORY_LIMIT=80
|
||||
MEMORY_RECENT_MESSAGES_LIMIT=8
|
||||
MEMORY_SUMMARY_TRIGGER_MESSAGES=20
|
||||
MEMORY_MAX_SUMMARY_CHARS=6000
|
||||
MEMORY_SUMMARY_USE_LLM=true
|
||||
MEMORY_INJECT_RECENT_MESSAGES=true
|
||||
MEMORY_INJECT_SUMMARY=true
|
||||
```
|
||||
|
||||
Estratégias disponíveis:
|
||||
|
||||
- `none`: não injeta memória conversacional no prompt.
|
||||
- `window`: injeta apenas as últimas mensagens.
|
||||
- `summary`: mantém resumo acumulado das mensagens antigas e últimas mensagens completas.
|
||||
|
||||
## Pontos implementados
|
||||
|
||||
Arquivos adicionados:
|
||||
|
||||
```text
|
||||
src/agent_framework/memory/summary_memory.py
|
||||
src/agent_framework/memory/summary_store.py
|
||||
```
|
||||
|
||||
Arquivos alterados:
|
||||
|
||||
```text
|
||||
src/agent_framework/memory/__init__.py
|
||||
src/agent_framework/config/settings.py
|
||||
src/agent_framework/runtime/agent_runtime.py
|
||||
src/agent_framework/persistence/sqlite_store.py
|
||||
src/agent_framework/persistence/oracle_store.py
|
||||
```
|
||||
|
||||
## Como usar no agente
|
||||
|
||||
Antes de chamar `build_messages()`, prepare a memória:
|
||||
|
||||
```python
|
||||
await self.prepare_memory_context(state)
|
||||
|
||||
messages = self.build_messages(
|
||||
state,
|
||||
system_prompt=system_prompt,
|
||||
user_text=state.get("sanitized_input"),
|
||||
)
|
||||
```
|
||||
|
||||
O método `prepare_memory_context()` salva o resultado em:
|
||||
|
||||
```python
|
||||
state["memory_context"]
|
||||
state["memory_context_metadata"]
|
||||
```
|
||||
|
||||
O método `build_messages()` injeta automaticamente esse contexto quando ele existe.
|
||||
|
||||
## Eventos de observabilidade
|
||||
|
||||
O runtime emite eventos IC quando a memória é carregada ou comprimida:
|
||||
|
||||
```text
|
||||
IC.MEMORY_CONTEXT_LOADED
|
||||
IC.MEMORY_COMPRESSION_TRIGGERED
|
||||
IC.MEMORY_SUMMARY_UPDATED
|
||||
```
|
||||
|
||||
## Persistência
|
||||
|
||||
SQLite:
|
||||
|
||||
```text
|
||||
agent_memory_summaries
|
||||
```
|
||||
|
||||
Oracle:
|
||||
|
||||
```text
|
||||
<ADB_TABLE_PREFIX>_MEMORY_SUMMARY
|
||||
```
|
||||
|
||||
MongoDB:
|
||||
|
||||
```text
|
||||
memory_summaries
|
||||
```
|
||||
|
||||
## Observação importante
|
||||
|
||||
`ConversationSummaryMemory` não é o mesmo que `Checkpoint Compaction`.
|
||||
|
||||
- Checkpoint compaction reduz checkpoints técnicos do LangGraph.
|
||||
- ConversationSummaryMemory reduz o contexto semântico da conversa para o LLM.
|
||||
54
libs/agent_framework/docs/DYNAMIC_LLM_PROFILES.md
Normal file
54
libs/agent_framework/docs/DYNAMIC_LLM_PROFILES.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Dynamic LLM Profiles
|
||||
|
||||
`llm_profiles.yaml` is optional.
|
||||
|
||||
If the file does not exist, the backend keeps the current behavior and uses `.env`:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=oci_openai
|
||||
OCI_GENAI_MODEL=openai.gpt-4.1
|
||||
LLM_TEMPERATURE=0.2
|
||||
LLM_MAX_TOKENS=2048
|
||||
```
|
||||
|
||||
If `llm_profiles.yaml` exists, each inference point resolves parameters in this order:
|
||||
|
||||
```text
|
||||
specific profile -> default profile -> .env
|
||||
```
|
||||
|
||||
Supported inference points:
|
||||
|
||||
| Profile | Used by |
|
||||
|---|---|
|
||||
| `default` | global fallback when YAML exists |
|
||||
| `supervisor` | global supervisor / LLM supervisor |
|
||||
| `router` | EnterpriseRouter LLM classification |
|
||||
| `guardrail` | optional LLM guardrail rail |
|
||||
| `grl` | optional output supervisor / GRL LLM rail and GRL advisor |
|
||||
| `judge` | LLM judge when enabled in `config/judges.yaml` |
|
||||
| `rag_rewriter` | RAG query rewriting |
|
||||
| `rag_compressor` | RAG context compression |
|
||||
| `rag_generation` | direct RAG answer generation |
|
||||
| `summary_memory` | ConversationSummaryMemory |
|
||||
| `noc` | optional NOC reasoning advisor |
|
||||
| `<agent_name>` | agent runtime, for example `billing_agent` |
|
||||
|
||||
Optional LLM inference points are disabled by default to preserve current behavior:
|
||||
|
||||
```env
|
||||
ENABLE_LLM_GUARDRAIL=false
|
||||
ENABLE_LLM_GRL=false
|
||||
ENABLE_RAG_QUERY_REWRITE=false
|
||||
ENABLE_RAG_CONTEXT_COMPRESSION=false
|
||||
ENABLE_RAG_GENERATION=false
|
||||
```
|
||||
|
||||
To enable guardrails/GRL, inject the same backend LLM object into the corresponding component and set the flags above as needed. For judges, do not use an extra LLM flag: enable or disable the LLM judge in `config/judges.yaml`.
|
||||
|
||||
|
||||
## Provider per profile
|
||||
|
||||
Recommendation: declare `provider` explicitly in every profile. The resolver can inherit it from `default`, but explicit provider avoids ambiguity and makes tests with invalid models/providers deterministic.
|
||||
|
||||
Judge activation is controlled by `config/judges.yaml`; `llm_profiles.yaml` only chooses the `judge` model/provider/params.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Guardrails calibrados adaptados ao framework
|
||||
|
||||
## Objetivo
|
||||
|
||||
Este pacote mantém a arquitetura atual do `agent_framework` e substitui a calibração interna dos rails pela lógica do pacote `guardrails.zip` anexado.
|
||||
|
||||
Foram preservados:
|
||||
|
||||
- `GuardrailPipeline`
|
||||
- execução paralela/fail-fast via `ParallelRailExecutor`
|
||||
- emissão de eventos GRL e eventos nomeados por rail
|
||||
- `OutputSupervisor`
|
||||
- perfis dinâmicos de LLM (`guardrail` e `grl`)
|
||||
- gravação do modelo no Langfuse pelo provider do próprio framework
|
||||
|
||||
## O que mudou
|
||||
|
||||
A lógica calibrada foi adicionada em:
|
||||
|
||||
```text
|
||||
src/agent_framework/guardrails/calibrated/
|
||||
```
|
||||
|
||||
A ponte com o LLM do framework foi adicionada em:
|
||||
|
||||
```text
|
||||
src/agent_framework/guardrails/framework_llm_client.py
|
||||
```
|
||||
|
||||
As classes públicas foram preservadas em:
|
||||
|
||||
```text
|
||||
src/agent_framework/guardrails/rails.py
|
||||
```
|
||||
|
||||
## Rails calibrados integrados
|
||||
|
||||
Input:
|
||||
|
||||
- `INPUT_SIZE`
|
||||
- `MSK`
|
||||
- `TOX`
|
||||
- `PINJ`
|
||||
- `VLOOP`
|
||||
- `DLEX_IN`
|
||||
- `OOS` opcional via `GUARDRAIL_OOS_ENABLED=true`
|
||||
|
||||
Output:
|
||||
|
||||
- `MSK`
|
||||
- `TOXOUT`
|
||||
- `CMP`
|
||||
- `AOFERTA`
|
||||
- `REVPREC`
|
||||
- `DLEX_OUT`
|
||||
- `GND`
|
||||
- `ALUC_RISK`
|
||||
|
||||
Retrieval:
|
||||
|
||||
- `RET_REL`
|
||||
- `RAGSEC`
|
||||
- `MSK`
|
||||
|
||||
## LLM e Langfuse
|
||||
|
||||
Os rails LLM não criam outro cliente fora do framework. Eles usam o `llm` passado ao `GuardrailPipeline`, chamando:
|
||||
|
||||
```python
|
||||
llm.ainvoke(..., profile_name="guardrail" ou "grl", component_name="guardrail.<code>")
|
||||
```
|
||||
|
||||
Assim, o modelo usado por `PINJ`, `OOS`, `AOFERTA`, `REVPREC`, `RAGSEC`, etc. continua aparecendo corretamente no Langfuse conforme a instrumentação atual do framework.
|
||||
|
||||
## Modo mock
|
||||
|
||||
Quando `USE_MOCK_LLM=true`, os rails LLM usam heurísticas locais calibradas para desenvolvimento/teste rápido.
|
||||
|
||||
Para validar com LLM real:
|
||||
|
||||
```bash
|
||||
export USE_MOCK_LLM=false
|
||||
```
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Guardrails and llm_profiles.yaml enforcement
|
||||
|
||||
This fix ensures calibrated guardrails respect `llm_profiles.yaml` for the `guardrail` and `grl` profiles.
|
||||
|
||||
## Problem
|
||||
|
||||
Some boot paths instantiated `GuardrailPipeline` without an explicit framework LLM. In that case the adapter treated `llm is None` as local mock mode and returned local fallback decisions. Also, `USE_MOCK_LLM=true` could hide the model configured in `profiles.guardrail` or `profiles.grl`.
|
||||
|
||||
That meant intentionally invalid models such as `xopenai.gpt-4.1` did not fail, because the guardrail never reached the configured provider.
|
||||
|
||||
## Fix
|
||||
|
||||
`framework_llm_client.py` now:
|
||||
|
||||
- resolves the selected profile before deciding mock vs real;
|
||||
- creates the framework LLM from `Settings` when the pipeline did not receive one;
|
||||
- gives precedence to an explicit non-mock `guardrail`/`grl` profile over `USE_MOCK_LLM`;
|
||||
- no longer overrides `temperature` and `max_tokens` at call time, so YAML profile values are honored.
|
||||
|
||||
`custom_rails.py` now allows passing `llm` and `observer` into the generated `GuardrailPipeline`.
|
||||
|
||||
## Expected validation
|
||||
|
||||
With this YAML:
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
default:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
guardrail:
|
||||
model: xopenai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 600
|
||||
grl:
|
||||
model: xopenai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 700
|
||||
```
|
||||
|
||||
LLM-based guardrails such as `PINJ` fallback, `AOFERTA`, `REVPREC`, `DLEX_OUT`, `RAGSEC`, and enabled LLM checks must attempt to use `xopenai.gpt-4.1` and surface the provider/model error instead of silently returning local mock results.
|
||||
|
||||
Deterministic short-circuit rails may still block before calling an LLM. To validate profile usage, use a case that reaches the LLM rail or inspect Langfuse generation metadata for `profile_name`, `model`, and `profile_source`.
|
||||
127
libs/agent_framework/docs/GUARDRAILS_PARALLELOS_OBSERVER_IC.md
Normal file
127
libs/agent_framework/docs/GUARDRAILS_PARALLELOS_OBSERVER_IC.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Guardrails paralelos fail-fast e Observer IC
|
||||
|
||||
## O que foi implementado
|
||||
|
||||
### 1. ParallelRailExecutor
|
||||
|
||||
Arquivo principal:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/guardrails/parallel_executor.py
|
||||
```
|
||||
|
||||
Também foi criado um alias de compatibilidade:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/guardrails/executor.py
|
||||
```
|
||||
|
||||
Esse alias evita erro quando algum código antigo importar:
|
||||
|
||||
```python
|
||||
from agent_framework.guardrails.executor import ParallelRailExecutor
|
||||
```
|
||||
|
||||
### 2. Execução paralela no GuardrailPipeline
|
||||
|
||||
Arquivo alterado:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/guardrails/pipeline.py
|
||||
```
|
||||
|
||||
O pipeline continua retornando o contrato antigo:
|
||||
|
||||
```python
|
||||
(texto_final, list[RailDecision])
|
||||
```
|
||||
|
||||
mas internamente pode executar rails em paralelo com fail-fast.
|
||||
|
||||
### 3. Execução paralela no OutputSupervisor
|
||||
|
||||
Arquivo alterado:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/guardrails/output_supervisor.py
|
||||
```
|
||||
|
||||
O `OutputSupervisor` agora usa `ParallelRailExecutor` quando habilitado.
|
||||
|
||||
### 4. Configuração
|
||||
|
||||
Novas configurações:
|
||||
|
||||
```env
|
||||
ENABLE_PARALLEL_GUARDRAILS=true
|
||||
GUARDRAILS_FAIL_FAST=true
|
||||
```
|
||||
|
||||
Também foram adicionadas em:
|
||||
|
||||
```text
|
||||
agent_framework/src/agent_framework/config/settings.py
|
||||
.env
|
||||
.env.example
|
||||
agent_template_backend/.env
|
||||
agent_template_backend_day_zero/.env
|
||||
```
|
||||
|
||||
### 5. Observer IC
|
||||
|
||||
O `AgentObserver` já tinha `emit_ic()`.
|
||||
|
||||
Foi complementada a API global compatível com FIRST/TIM:
|
||||
|
||||
```python
|
||||
from agent_framework.observer import ic, aic, noc, anoc, grl, agrl
|
||||
```
|
||||
|
||||
Exemplos:
|
||||
|
||||
```python
|
||||
ic("AGENT_COMPLETED", data={"session_id": "..."})
|
||||
await aic("MCP_TOOL_CALLED", data={"tool_name": "consultar_fatura"})
|
||||
```
|
||||
|
||||
### 6. ICs automáticos no template backend
|
||||
|
||||
O backend emite agora:
|
||||
|
||||
```text
|
||||
IC.AGENT_STARTED
|
||||
IC.ROUTE_SELECTED
|
||||
IC.MCP_TOOL_CALLED
|
||||
IC.TOOL_CALLED
|
||||
IC.AGENT_COMPLETED
|
||||
```
|
||||
|
||||
Além dos eventos já existentes:
|
||||
|
||||
```text
|
||||
NOC.001
|
||||
NOC.005
|
||||
NOC.006
|
||||
GRL.001 ... GRL.009
|
||||
```
|
||||
|
||||
## Validações executadas
|
||||
|
||||
Foram executadas validações locais com `PYTHONPATH=agent_framework/src`:
|
||||
|
||||
```bash
|
||||
python3 -m compileall -q agent_framework/src/agent_framework agent_template_backend/app agent_template_backend_day_zero/app
|
||||
```
|
||||
|
||||
Smoke tests executados:
|
||||
|
||||
```text
|
||||
1. Import de ParallelRailExecutor via agent_framework.guardrails
|
||||
2. Import de ParallelRailExecutor via agent_framework.guardrails.executor
|
||||
3. Execução fail-fast: FastBlock cancela SlowAllow
|
||||
4. GuardrailPipeline paralelo retorna RailDecision legado
|
||||
5. OutputSupervisor paralelo retorna RailAction.BLOCK
|
||||
6. API global observer.ic/noc/grl/aic/anoc/agrl
|
||||
```
|
||||
|
||||
Observação: o import completo do `agent_template_backend.app.workflows.agent_graph` depende de `langgraph`, que não está instalado no sandbox de validação. O arquivo foi validado por `compileall`, e a dependência já consta em `agent_template_backend/requirements.txt`.
|
||||
22
libs/agent_framework/docs/GUARDRAILS_REASON_REAL_FIX.md
Normal file
22
libs/agent_framework/docs/GUARDRAILS_REASON_REAL_FIX.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Correção: `reason` real nos guardrails calibrados
|
||||
|
||||
Esta versão corrige o fallback local dos guardrails calibrados para não emitir razões genéricas como `mock PINJ calibrado`.
|
||||
|
||||
Mesmo quando `USE_MOCK_LLM=true`, os rails agora retornam uma razão operacional baseada no marcador ou padrão que disparou a decisão.
|
||||
|
||||
Exemplos:
|
||||
|
||||
- `PINJ`: informa o padrão determinístico de prompt injection/jailbreak detectado.
|
||||
- `REVPREC`: informa o marcador de verbalização prematura encontrado.
|
||||
- `AOFERTA`: informa o marcador de oferta proativa detectado.
|
||||
- `TOX`: informa o padrão determinístico de toxicidade detectado.
|
||||
- `OOS`: informa o marcador fora de escopo encontrado.
|
||||
- `RAGSEC`, `DLEX_IN` e `DLEX_OUT`: informam o padrão local de risco quando o fallback local estiver ativo.
|
||||
|
||||
A arquitetura atual foi preservada:
|
||||
|
||||
- `GuardrailPipeline`
|
||||
- `ParallelRailExecutor`
|
||||
- emissão GRL
|
||||
- execução paralela/fail-fast
|
||||
- uso de `llm_profiles.yaml` via LLM do framework quando `USE_MOCK_LLM=false`
|
||||
72
libs/agent_framework/docs/GUARDRAILS_YAML_SOURCE_OF_TRUTH.md
Normal file
72
libs/agent_framework/docs/GUARDRAILS_YAML_SOURCE_OF_TRUTH.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# guardrails.yaml como fonte da verdade
|
||||
|
||||
## Problema corrigido
|
||||
|
||||
O framework estava instanciando o bundle default de guardrails diretamente dentro de `GuardrailPipeline` e `CustomRails`.
|
||||
Na prática, isso fazia com que todos os guardrails disponíveis fossem executados mesmo quando `config/guardrails.yaml` declarava apenas alguns rails habilitados.
|
||||
|
||||
## Regra atual
|
||||
|
||||
Agora a regra é:
|
||||
|
||||
```text
|
||||
Se config/guardrails.yaml existir:
|
||||
somente os rails listados e enabled=true serão executados.
|
||||
|
||||
Se config/guardrails.yaml não existir:
|
||||
o framework mantém o comportamento legado e carrega o bundle default.
|
||||
```
|
||||
|
||||
## Exemplo
|
||||
|
||||
```yaml
|
||||
input:
|
||||
- code: MSK
|
||||
enabled: true
|
||||
- code: VLOOP
|
||||
enabled: true
|
||||
|
||||
output:
|
||||
- code: REVPREC
|
||||
enabled: true
|
||||
```
|
||||
|
||||
Com esse arquivo, o input executa apenas `MSK` e `VLOOP`, e o output executa apenas `REVPREC`.
|
||||
Guardrails como `PINJ`, `TOX`, `DLEX_IN`, `AOFERTA`, `DLEX_OUT`, `CMP` e `RAGSEC` não são instanciados se não estiverem no YAML.
|
||||
|
||||
## Guardrail LLM
|
||||
|
||||
Quando um rail LLM está habilitado no YAML, ele usa o profile adequado do `llm_profiles.yaml`:
|
||||
|
||||
```text
|
||||
PINJ, TOX, OOS, DLEX_IN, RAGSEC -> profile guardrail
|
||||
REVPREC, AOFERTA, DLEX_OUT -> profile grl
|
||||
```
|
||||
|
||||
Se o modelo do profile estiver errado, o erro não deve ser escondido por fallback silencioso.
|
||||
|
||||
## Rails conhecidos
|
||||
|
||||
Principais códigos aceitos:
|
||||
|
||||
```text
|
||||
INPUT_SIZE
|
||||
MSK
|
||||
TOX
|
||||
PINJ
|
||||
VLOOP
|
||||
DLEX_IN
|
||||
OOS
|
||||
TOXOUT
|
||||
CMP
|
||||
AOFERTA
|
||||
REVPREC
|
||||
DLEX_OUT
|
||||
GND
|
||||
ALUC_RISK
|
||||
RET_REL
|
||||
RAGSEC
|
||||
TOOL_VAL
|
||||
```
|
||||
|
||||
Também existem aliases de compatibilidade, como `JAILBREAK`, `GROUNDEDNESS`, `HALLUCINATION_RISK`, `TOX_OUT`, `MSK_OUT` e `TOOL_VALIDATION`.
|
||||
67
libs/agent_framework/docs/IC_NOC_GRL_LANGFUSE_NATIVE.md
Normal file
67
libs/agent_framework/docs/IC_NOC_GRL_LANGFUSE_NATIVE.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# IC/NOC/GRL nativo no Langfuse
|
||||
|
||||
Esta versão do framework remove a necessidade de um `ics_collector.py` dentro de cada agente.
|
||||
|
||||
Agora o próprio framework publica eventos `IC.*`, `AGA.*`, `NOC.*` e `GRL.*` no Langfuse por meio do `AgentObserver` e do `LangfuseAnalyticsPublisher`.
|
||||
|
||||
## Configuração
|
||||
|
||||
Para publicar IC/NOC/GRL no Langfuse:
|
||||
|
||||
```env
|
||||
ENABLE_LANGFUSE=true
|
||||
LANGFUSE_PUBLIC_KEY=pk-lf-...
|
||||
LANGFUSE_SECRET_KEY=sk-lf-...
|
||||
LANGFUSE_HOST=http://localhost:3005
|
||||
|
||||
# Opcional. Se não informar, ENABLE_LANGFUSE=true já inclui langfuse no observer.
|
||||
ENABLE_ANALYTICS=true
|
||||
ANALYTICS_PROVIDERS=langfuse,oci_streaming
|
||||
```
|
||||
|
||||
Para manter compatibilidade com projetos antigos:
|
||||
|
||||
```python
|
||||
from agent_framework.observer import configure
|
||||
|
||||
configure({"publisher": {"type": "langfuse"}})
|
||||
```
|
||||
|
||||
## Emissão em agentes nativos
|
||||
|
||||
```python
|
||||
from agent_framework.observer import event, ic, noc, grl
|
||||
|
||||
# Mantém o código exatamente como o backoffice original mostrava no Langfuse.
|
||||
event("AGA.001", data={"sessionId": session_id, "agentId": "backoffice"})
|
||||
|
||||
# Também pode usar os atalhos.
|
||||
ic("AGA.018", data={"missingFields": ["gsm"]})
|
||||
noc("NOC.001", data={"sessionId": session_id})
|
||||
grl("GRL.004", data={"rail": "PINJ", "blocked": True})
|
||||
```
|
||||
|
||||
## Comportamento esperado no Langfuse
|
||||
|
||||
Cada evento vira uma observation/span com `name` igual ao código:
|
||||
|
||||
- `AGA.001`
|
||||
- `AGA.018`
|
||||
- `NOC.001`
|
||||
- `GRL.004`
|
||||
|
||||
A metadata recebe automaticamente:
|
||||
|
||||
- `tag`
|
||||
- `ic=true` para `IC.*` e `AGA.*`
|
||||
- `noc=true` para `NOC.*`
|
||||
- `grl=true` para `GRL.*`
|
||||
- `sessionId`, `messageId`, `agentId`, `channelId` quando existirem no payload
|
||||
|
||||
## Compatibilidade TIM/FIRST
|
||||
|
||||
`ic("AGA.001")` não vira `IC.AGA.001`. O framework preserva `AGA.001`, porque no backoffice original esse era o contrato exibido no Langfuse.
|
||||
|
||||
`noc("001")` vira `NOC.001`.
|
||||
|
||||
`grl("004")` vira `GRL.004`.
|
||||
90
libs/agent_framework/docs/JUDGES_CALIBRATED_ADAPTATION.md
Normal file
90
libs/agent_framework/docs/JUDGES_CALIBRATED_ADAPTATION.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# Calibrated Judges Adaptation
|
||||
|
||||
This project now carries the calibrated judge package inside the framework while preserving the existing architecture.
|
||||
|
||||
## What changed
|
||||
|
||||
The calibrated judge prompts were added under:
|
||||
|
||||
```text
|
||||
src/agent_framework/judges/calibrated/
|
||||
```
|
||||
|
||||
The main integration point remains:
|
||||
|
||||
```text
|
||||
src/agent_framework/judges/judge.py
|
||||
```
|
||||
|
||||
The framework still uses:
|
||||
|
||||
- `JudgePipeline`
|
||||
- `config/judges.yaml` as the source of truth for which judges run
|
||||
- `llm_profiles.yaml` profile `judge` for provider/model/temperature/max tokens
|
||||
- the existing framework LLM provider, Langfuse instrumentation and token accounting
|
||||
- `.env` fallback when `llm_profiles.yaml` is absent
|
||||
|
||||
There is intentionally no `ENABLE_LLM_JUDGE` gate.
|
||||
|
||||
## Current mapping
|
||||
|
||||
With this YAML:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
- name: groundedness
|
||||
enabled: true
|
||||
threshold: 0.6
|
||||
```
|
||||
|
||||
The framework runs:
|
||||
|
||||
| YAML name | Calibrated task | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `response_quality` | `RQLT` | response quality |
|
||||
| `groundedness` | `ALUC` | hallucination / unsupported factual claims |
|
||||
|
||||
Both use the `judge` LLM profile unless another profile is set on the YAML item.
|
||||
|
||||
## Testing model enforcement
|
||||
|
||||
If this is configured:
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
judge:
|
||||
provider: oci_openai
|
||||
model: xopenai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 800
|
||||
```
|
||||
|
||||
Then `response_quality` and `groundedness` will try to use `xopenai.gpt-4.1`. If that model does not exist, the calibrated judge call should fail according to the entry/global `fail_closed` behavior.
|
||||
|
||||
## Keeping the old heuristic judges
|
||||
|
||||
To force the old deterministic behavior for a specific judge:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: response_quality
|
||||
type: deterministic
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
```
|
||||
|
||||
## Optional calibrated judges
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: tone
|
||||
enabled: true
|
||||
- name: sentiment
|
||||
enabled: true
|
||||
fail_on_negative: false
|
||||
```
|
||||
|
||||
These map to the calibrated `VCTN` and `CSI` prompts.
|
||||
33
libs/agent_framework/docs/JUDGES_YAML_SIMPLE_SCHEMA.md
Normal file
33
libs/agent_framework/docs/JUDGES_YAML_SIMPLE_SCHEMA.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# judges.yaml simple schema
|
||||
|
||||
The framework accepts the simple judge configuration format:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
- name: groundedness
|
||||
enabled: true
|
||||
threshold: 0.6
|
||||
```
|
||||
|
||||
In this format, `type` is optional. The framework infers deterministic judges from `name`:
|
||||
|
||||
- `response_quality` -> deterministic response quality judge
|
||||
- `groundedness` -> deterministic groundedness judge
|
||||
|
||||
The `threshold` field is now applied to the deterministic judge pass/fail calculation and is also emitted in the judge result metadata.
|
||||
|
||||
No LLM is called by this YAML. The `llm_profiles.yaml` profile named `judge` is only used if a LLM judge is explicitly declared, for example:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: llm_judge
|
||||
type: llm
|
||||
enabled: true
|
||||
profile: judge
|
||||
fail_closed: true
|
||||
```
|
||||
|
||||
There is no `ENABLE_LLM_JUDGE` gate. The YAML is the source of truth.
|
||||
40
libs/agent_framework/docs/JUDGES_YAML_SOURCE_OF_TRUTH.md
Normal file
40
libs/agent_framework/docs/JUDGES_YAML_SOURCE_OF_TRUTH.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Judges YAML as the source of truth
|
||||
|
||||
This version removes the extra `ENABLE_LLM_JUDGE` activation gate.
|
||||
|
||||
The judge stage now follows this rule:
|
||||
|
||||
1. `ENABLE_JUDGES=false` disables the whole judge stage.
|
||||
2. `config/judges.yaml` decides which judges are active.
|
||||
3. If a judge has `type: llm` and `enabled: true`, the framework calls the LLM.
|
||||
4. `llm_profiles.yaml` decides which model/provider that LLM judge uses through the configured profile, usually `judge`.
|
||||
5. If `llm_profiles.yaml` is absent, the LLM judge falls back to the global `.env` LLM configuration.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- code: llm_judge
|
||||
type: llm
|
||||
enabled: true
|
||||
profile: judge
|
||||
fail_closed: true
|
||||
```
|
||||
|
||||
And in `llm_profiles.yaml`:
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
judge:
|
||||
provider: oci_openai
|
||||
model: openai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 800
|
||||
```
|
||||
|
||||
If you intentionally configure a nonexistent model for `judge`, the LLM judge will try to use it. The final behavior depends on `fail_closed`:
|
||||
|
||||
- `fail_closed: true` blocks/fails the judge result.
|
||||
- `fail_closed: false` reports the LLM judge as unavailable and follows fail-open.
|
||||
|
||||
`ENABLE_LLM_JUDGE` is intentionally not used anymore.
|
||||
60
libs/agent_framework/docs/JUDGE_MODEL_ERROR_FAIL_CLOSED.md
Normal file
60
libs/agent_framework/docs/JUDGE_MODEL_ERROR_FAIL_CLOSED.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Judge model/profile error handling
|
||||
|
||||
The calibrated judges (`response_quality`, `groundedness`, `sentiment`, `tone`, `llm_judge`) are LLM-based unless an entry explicitly declares `type: deterministic`.
|
||||
|
||||
Because they depend on the model configured in `llm_profiles.yaml`, the default behavior is now **fail-closed**:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
- name: groundedness
|
||||
enabled: true
|
||||
threshold: 0.6
|
||||
```
|
||||
|
||||
With this configuration, if the profile `judge` points to an invalid model, for example:
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
judge:
|
||||
provider: oci_openai
|
||||
model: xopenai.gpt-4.1
|
||||
```
|
||||
|
||||
then the judge result is returned as `passed=false`, with `score=0.0`, the exception metadata, and a reason similar to:
|
||||
|
||||
```text
|
||||
Falha no judge calibrado RQLT: ...
|
||||
```
|
||||
|
||||
To intentionally keep the old fail-open behavior, configure it explicitly in `judges.yaml`:
|
||||
|
||||
```yaml
|
||||
fail_closed: false
|
||||
judges:
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
```
|
||||
|
||||
Or per judge:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: response_quality
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
fail_closed: false
|
||||
```
|
||||
|
||||
Deterministic judges do not call the LLM profile:
|
||||
|
||||
```yaml
|
||||
judges:
|
||||
- name: response_quality
|
||||
type: deterministic
|
||||
enabled: true
|
||||
threshold: 0.7
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
# Langfuse analytics context correlation fix
|
||||
|
||||
This patch fixes a remaining trace-splitting issue in the Langfuse analytics publisher.
|
||||
|
||||
## Problem
|
||||
|
||||
After the framework trace-id normalization fix, the main HTTP/workflow trace was being created correctly, but some IC/NOC/GRL events could still appear as separate root traces in Langfuse, especially events such as:
|
||||
|
||||
- `IC.BACKOFFICE_WORKFLOW_COMPLETED`
|
||||
- `IC.BACKOFFICE_NODE_COMPLETED`
|
||||
- `NOC.*`
|
||||
|
||||
This happened when those analytics events carried only business identifiers such as `transaction_id` or `sessionId`, while the HTTP trace was correlated by `request_id`.
|
||||
|
||||
## Fix
|
||||
|
||||
`src/agent_framework/analytics/providers/langfuse.py` now merges the current `ObservabilityContext` into analytics event payloads before computing the Langfuse trace context.
|
||||
|
||||
Correlation priority is now:
|
||||
|
||||
1. Current `trace_id` / `request_id` from `ObservabilityContext`
|
||||
2. Payload `trace_id` / `request_id`
|
||||
3. Business fallback: `transaction_id`, `session_id`, `sessionId`
|
||||
|
||||
This keeps business IDs in metadata, but ensures Langfuse observations are attached to the active request trace whenever a workflow is running.
|
||||
|
||||
## Expected result
|
||||
|
||||
In Langfuse `Tracing > Traces`, a single backoffice request should appear as one main trace, such as:
|
||||
|
||||
- `http.request.completed`
|
||||
- or the configured request/workflow root name
|
||||
|
||||
Inside that trace, the internal observations should include:
|
||||
|
||||
- `backoffice.channel.normalized`
|
||||
- `backoffice.workflow.dispatch`
|
||||
- `langgraph.node.*`
|
||||
- `mcp.tool_call.*`
|
||||
- `IC.BACKOFFICE_*`
|
||||
- `NOC.*`
|
||||
- guardrails and judges
|
||||
|
||||
IC/NOC/GRL events should no longer create a separate trace just because they only carried `transaction_id` or `sessionId`.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Langfuse Internal Event Root Trace Fix
|
||||
|
||||
## Problema
|
||||
|
||||
Alguns eventos internos IC/NOC/GRL estavam aparecendo na tela **Tracing → Traces** como traces raiz separados, mesmo quando pertenciam à mesma execução REST/workflow.
|
||||
|
||||
Exemplo observado:
|
||||
|
||||
```text
|
||||
Name: http.request.completed
|
||||
Input: {"eventType": "NOC.006", ...}
|
||||
Output: {"published": true}
|
||||
```
|
||||
|
||||
Esse registro não representa a execução real do agente. Ele representa apenas a publicação de um evento interno via analytics, e por isso não deve aparecer como trace raiz.
|
||||
|
||||
## Correção
|
||||
|
||||
O arquivo abaixo foi ajustado:
|
||||
|
||||
```text
|
||||
src/agent_framework/analytics/providers/langfuse.py
|
||||
```
|
||||
|
||||
A nova regra é:
|
||||
|
||||
```text
|
||||
1 request/workflow = 1 trace raiz
|
||||
IC/NOC/GRL = observations/spans dentro do trace corrente
|
||||
Eventos internos embrulhados em http.request/gateway/telemetry não criam trace raiz
|
||||
```
|
||||
|
||||
## Regras aplicadas
|
||||
|
||||
O publisher agora:
|
||||
|
||||
1. Detecta envelopes internos como `IC.*`, `NOC.*`, `GRL.*` e `AGA.*`.
|
||||
2. Suprime eventos técnicos do tipo `http.request.completed` cujo input real é um envelope interno como `NOC.006`.
|
||||
3. Prioriza correlação por `ObservabilityContext`:
|
||||
|
||||
```text
|
||||
trace_id/request_id do contexto atual
|
||||
> trace_id/request_id do payload
|
||||
> transaction_id/session_id apenas como fallback
|
||||
```
|
||||
|
||||
4. Evita fallback para `langfuse.trace(...)` ou `langfuse.span(...)` para eventos internos/técnicos quando a observation correlacionada falha.
|
||||
5. Mantém a flag abaixo para debug isolado:
|
||||
|
||||
```bash
|
||||
export LANGFUSE_ALLOW_STANDALONE_INTERNAL_EVENTS=true
|
||||
```
|
||||
|
||||
Por padrão, essa flag deve ficar desligada.
|
||||
|
||||
## Resultado esperado
|
||||
|
||||
Na tela **Tracing → Traces**, uma execução nova deve aparecer como uma linha principal, por exemplo:
|
||||
|
||||
```text
|
||||
http.request.completed
|
||||
```
|
||||
|
||||
ou:
|
||||
|
||||
```text
|
||||
backoffice.process-and-stream
|
||||
```
|
||||
|
||||
Ao abrir o trace, devem aparecer internamente:
|
||||
|
||||
```text
|
||||
IC.BACKOFFICE_WORKFLOW_COMPLETED
|
||||
NOC.006
|
||||
langgraph.node.*
|
||||
mcp.tool_call.*
|
||||
guardrail.*
|
||||
judge.*
|
||||
```
|
||||
|
||||
O trace solto com `Input: {"eventType": "NOC.006"}` e `Output: {"published": true}` deve desaparecer.
|
||||
56
libs/agent_framework/docs/LANGFUSE_SPAN_HIERARCHY_FIX.md
Normal file
56
libs/agent_framework/docs/LANGFUSE_SPAN_HIERARCHY_FIX.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Langfuse Span Hierarchy Fix
|
||||
|
||||
## Problem
|
||||
|
||||
After trace correlation was fixed, a full request no longer exploded into many independent Langfuse traces. However, observations inside the trace could appear flattened at the same level.
|
||||
|
||||
This happened because the framework was propagating only the Langfuse `trace_id`, but not the current parent observation/span id.
|
||||
|
||||
In Langfuse, a tree needs both:
|
||||
|
||||
- `trace_id`: identifies the root execution trace;
|
||||
- `parent_span_id`: identifies which observation/span should be the parent of the new observation.
|
||||
|
||||
Without `parent_span_id`, all observations are correlated to the same trace but may appear as direct children of the trace root.
|
||||
|
||||
## Fix
|
||||
|
||||
The framework now keeps the current Langfuse observation id in the async observability context.
|
||||
|
||||
Updated files:
|
||||
|
||||
- `src/agent_framework/observability/context.py`
|
||||
- `src/agent_framework/observability/telemetry.py`
|
||||
- `src/agent_framework/analytics/providers/langfuse.py`
|
||||
|
||||
## Behavior
|
||||
|
||||
When a span starts:
|
||||
|
||||
1. The framework creates the Langfuse observation.
|
||||
2. It extracts the observation id from the SDK object.
|
||||
3. It stores that id in a ContextVar as the current parent observation.
|
||||
4. Nested spans, generations and analytics events pass it as `trace_context.parent_span_id`.
|
||||
5. When the span exits, the previous parent observation id is restored.
|
||||
|
||||
## Expected Langfuse Structure
|
||||
|
||||
A backoffice request should appear as one trace, with nested observations such as:
|
||||
|
||||
```text
|
||||
http.request / backoffice.process-and-stream
|
||||
└── backoffice.workflow.dispatch
|
||||
├── langgraph.node.framework_input_guardrails
|
||||
├── langgraph.node.fetch_ticket
|
||||
├── langgraph.node.validation
|
||||
├── langgraph.node.imdb_enrichment
|
||||
│ └── mcp.tool_call.consultar_imdb_cliente
|
||||
├── langgraph.node.knowledge_base_enrichment
|
||||
│ └── mcp.tool_call.consultar_tais_kb
|
||||
├── langgraph.node.treatment_decision
|
||||
└── langgraph.node.siebel_sr_opening
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
This fix complements the previous trace correlation fixes. Those fixes solved root trace duplication. This fix solves parent-child hierarchy inside the trace.
|
||||
107
libs/agent_framework/docs/LANGFUSE_TRACE_CORRELATION_FIX.md
Normal file
107
libs/agent_framework/docs/LANGFUSE_TRACE_CORRELATION_FIX.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Langfuse trace correlation fix
|
||||
|
||||
## Problem
|
||||
|
||||
The Langfuse **Tracing > Traces** list was showing one row per internal framework event, for example:
|
||||
|
||||
- `langgraph.node.started`
|
||||
- `langgraph.node.fetch_ticket`
|
||||
- `OpenAI-generation`
|
||||
- `TaisKbClient.search_documents`
|
||||
- `NOC.001`
|
||||
|
||||
That is not the intended observability model.
|
||||
|
||||
The intended model is:
|
||||
|
||||
```text
|
||||
1 REST/SSE/workflow request = 1 Langfuse trace
|
||||
internal steps = observations/spans/generations inside that trace
|
||||
```
|
||||
|
||||
## Root causes
|
||||
|
||||
1. `Telemetry.event(...)` used raw `langfuse.event(...)` when available. Depending on the SDK/context, this creates a new top-level trace for every event.
|
||||
2. `Telemetry.generation(...)` preferred raw `langfuse.generation(...)`, which can also create top-level traces when no current Langfuse observation is active.
|
||||
3. `LangfuseAnalyticsPublisher` for IC/NOC/GRL also created standalone observations without a deterministic trace context.
|
||||
4. The LLM provider used `langfuse.openai.AsyncOpenAI` whenever Langfuse was enabled. This auto-instrumentation can create separate `OpenAI-generation` traces. The framework already emits correlated LLM generations through `Telemetry.generation(...)`, so the wrapper caused noisy duplicate top-level traces.
|
||||
|
||||
## What was changed
|
||||
|
||||
### `agent_framework/observability/telemetry.py`
|
||||
|
||||
- Added deterministic Langfuse trace correlation using `trace_id` / `request_id` / `session_id`.
|
||||
- Injects `trace_context={"trace_id": ...}` when starting Langfuse observations/generations, with backward-compatible TypeError fallback.
|
||||
- `Telemetry.event(...)` no longer calls raw `langfuse.event(...)` first.
|
||||
- `Telemetry.generation(...)` no longer prefers raw `langfuse.generation(...)`; it prefers correlated current generation/observation APIs.
|
||||
- When a span has no explicit `trace_id`, it uses the request id as the trace id and stores it in the observability context.
|
||||
|
||||
### `agent_framework/analytics/providers/langfuse.py`
|
||||
|
||||
- Added deterministic trace correlation for IC/NOC/GRL analytics events.
|
||||
- Injects `trace_context` into `start_as_current_observation(...)`.
|
||||
- Avoids raw `langfuse.event(...)` fallback.
|
||||
- For legacy SDKs, attempts to create/reuse a deterministic trace with `langfuse.trace(id=...)` and attach spans to it.
|
||||
|
||||
### `agent_framework/llm/providers.py`
|
||||
|
||||
- Langfuse OpenAI auto-instrumentation is now opt-in.
|
||||
- Default behavior uses the standard `openai.AsyncOpenAI` client and relies on the framework's own `Telemetry.generation(...)` to create correlated Langfuse generations.
|
||||
- To re-enable wrapper-based auto-instrumentation, set:
|
||||
|
||||
```env
|
||||
ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true
|
||||
```
|
||||
|
||||
For this framework, the recommended default is to keep it disabled.
|
||||
|
||||
## Expected result
|
||||
|
||||
In Langfuse **Tracing > Traces**, you should see one row per business execution, for example:
|
||||
|
||||
```text
|
||||
POST /agent/process-and-stream | man-da8657ac
|
||||
POST /agent/process-ticket | man-fec67d60
|
||||
```
|
||||
|
||||
When opening a trace, you should see internal observations such as:
|
||||
|
||||
```text
|
||||
http.request
|
||||
channel_gateway
|
||||
backoffice.workflow.dispatch
|
||||
langgraph.node.framework_input_guardrails
|
||||
langgraph.node.fetch_ticket
|
||||
langgraph.node.validation
|
||||
langgraph.node.imdb_enrichment
|
||||
mcp.tool_call.consultar_imdb_cliente
|
||||
langgraph.node.treatment_decision
|
||||
langgraph.node.siebel_sr_opening
|
||||
framework_output_guardrails
|
||||
framework_judges
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Run the backend and execute one request. Then verify:
|
||||
|
||||
1. The `Traces` screen has one trace row for the request, not one row per node.
|
||||
2. `OpenAI-generation` no longer appears as a separate top-level trace unless `ENABLE_LANGFUSE_OPENAI_AUTO_INSTRUMENTATION=true`.
|
||||
3. LangGraph node events and IC/NOC/GRL events appear under the same request trace.
|
||||
|
||||
|
||||
## Fix adicional: formato do trace_id no Langfuse SDK v3
|
||||
|
||||
O Langfuse SDK v3 exige que `trace_context.trace_id` seja exatamente um valor hexadecimal minúsculo com 32 caracteres.
|
||||
Como o framework usa IDs de negócio como UUID com hífens (`d411b925-a096-...`) ou session ids (`man-bcbe3e05`), esses valores agora são normalizados antes de serem enviados ao Langfuse:
|
||||
|
||||
- UUID com hífens: remove hífens e reaproveita o hexadecimal de 32 caracteres;
|
||||
- qualquer outro identificador: gera um hash MD5 determinístico de 32 caracteres;
|
||||
- o valor original continua preservado em metadata como `framework_trace_id`;
|
||||
- o valor aceito pelo Langfuse fica em `langfuse_trace_id`.
|
||||
|
||||
Isso evita erros como:
|
||||
|
||||
```text
|
||||
ValueError: invalid literal for int() with base 16: 'd411b925-a096-465c-adf2-186623b82c19'
|
||||
```
|
||||
16
libs/agent_framework/docs/LEGACY_OUTPUT_GUARDRAIL_REMOVAL.md
Normal file
16
libs/agent_framework/docs/LEGACY_OUTPUT_GUARDRAIL_REMOVAL.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Remoção do LEGACY_OUTPUT_GUARDRAIL
|
||||
|
||||
`LEGACY_OUTPUT_GUARDRAIL` era um sinal de compatibilidade associado ao guardrail LLM genérico/catch-all do pipeline antigo.
|
||||
|
||||
Na arquitetura atual, os rails calibrados já executam suas próprias decisões e emitem GRL com códigos de negócio específicos, por exemplo `PINJ`, `TOX`, `REVPREC`, `AOFERTA`, `DLEX_OUT` e `RAGSEC`.
|
||||
|
||||
Por isso, o emit legado foi removido/suprimido para evitar ruído e duplicidade no Langfuse.
|
||||
|
||||
## Regra atual
|
||||
|
||||
- Mantém `GRL.001` a `GRL.009` para ciclo e resultado do pipeline.
|
||||
- Mantém eventos nomeados dos rails calibrados, como `GRL.REVPREC` e `guardrail.output.REVPREC.completed`.
|
||||
- Suprime eventos genéricos/legados: `LEGACY_OUTPUT_GUARDRAIL`, `LLM_GUARDRAIL` e `LLM_GRL`.
|
||||
- Remove o auto-append do guardrail LLM genérico controlado por `ENABLE_LLM_GUARDRAIL`.
|
||||
|
||||
O uso de LLM permanece nos rails calibrados que precisam dele, usando `llm_profiles.yaml` com os profiles `guardrail` e `grl`.
|
||||
43
libs/agent_framework/docs/LLM_PROFILES_PROVIDER_EXPLICIT.md
Normal file
43
libs/agent_framework/docs/LLM_PROFILES_PROVIDER_EXPLICIT.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Explicit provider in llm_profiles.yaml
|
||||
|
||||
Each LLM profile should declare `provider` explicitly. The resolver can still inherit
|
||||
missing keys from `profiles.default` and then from `.env`, but explicit `provider`
|
||||
per profile is safer because each inference point clearly states which client must
|
||||
be used.
|
||||
|
||||
Rules:
|
||||
|
||||
- If `llm_profiles.yaml` exists, the selected profile overrides `.env`.
|
||||
- Missing keys in a selected profile fall back to `profiles.default`.
|
||||
- Missing keys in `profiles.default` fall back to `.env`.
|
||||
- `judges.yaml` decides whether an LLM judge exists. There is no
|
||||
`ENABLE_LLM_JUDGE` gate and no `LLM_JUDGE_FAIL_CLOSED` setting. Judge
|
||||
fail-open/fail-closed behavior belongs in `judges.yaml`.
|
||||
- `llm_profiles.yaml` only chooses provider/model/params for the judge profile.
|
||||
|
||||
Example test:
|
||||
|
||||
```yaml
|
||||
profiles:
|
||||
judge:
|
||||
provider: oci_openai
|
||||
model: xopenai.gpt-4.1
|
||||
temperature: 0
|
||||
max_tokens: 800
|
||||
```
|
||||
|
||||
And enable the LLM judge in `config/judges.yaml`:
|
||||
|
||||
```yaml
|
||||
enabled: true
|
||||
fail_closed: true
|
||||
judges:
|
||||
- code: llm_judge
|
||||
type: llm
|
||||
enabled: true
|
||||
profile: judge
|
||||
fail_closed: true
|
||||
```
|
||||
|
||||
With that setup, the invalid model must be used by the judge profile and the
|
||||
judge must fail closed.
|
||||
204
libs/agent_framework/docs/MCP_CACHE.md
Normal file
204
libs/agent_framework/docs/MCP_CACHE.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# MCP Cache
|
||||
|
||||
O cache MCP é configurado diretamente no `config/tools.yaml`, dentro da própria tool.
|
||||
|
||||
Não existe regra chumbada por nome, idioma ou prefixo. Uma tool só usa cache quando declarar explicitamente:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
consultar_fatura:
|
||||
description: Consulta dados resumidos de fatura por msisdn/invoice_id.
|
||||
mcp_server: telecom
|
||||
enabled: true
|
||||
cache:
|
||||
enabled: true
|
||||
ttl_seconds: 600
|
||||
args_schema:
|
||||
msisdn: string
|
||||
invoice_id: string
|
||||
```
|
||||
|
||||
Tools sem bloco `cache` não usam cache por padrão:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
consultar_titulo_financeiro:
|
||||
description: Consulta um título financeiro por cliente e contrato.
|
||||
mcp_server: telecom
|
||||
enabled: true
|
||||
args_schema:
|
||||
customer_id: string
|
||||
contract_id: string
|
||||
```
|
||||
|
||||
Tools de ação devem ficar sem cache ou com cache explicitamente desabilitado:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
solicitar_troca:
|
||||
description: Simula abertura de solicitação de troca.
|
||||
mcp_server: retail
|
||||
enabled: true
|
||||
tool_type: action
|
||||
requires: [order_id, reason]
|
||||
confirmation_required: false
|
||||
cache:
|
||||
enabled: false
|
||||
args_schema:
|
||||
order_id: string
|
||||
reason: string
|
||||
```
|
||||
|
||||
|
||||
## Como a `cache_key` é montada
|
||||
|
||||
A chave de cache MCP precisa ser determinística. Ela não pode depender de valores que mudam a cada turno, como `session_id`, `request_id`, `trace_id`, `timestamp`, `intent`, `agent_id` ou `business_context` completo.
|
||||
|
||||
A regra implementada é:
|
||||
|
||||
```text
|
||||
mesma tool + mesmos campos declarados no args_schema + mesmos valores = mesma cache_key
|
||||
```
|
||||
|
||||
Exemplo:
|
||||
|
||||
```yaml
|
||||
tools:
|
||||
consultar_fatura:
|
||||
cache:
|
||||
enabled: true
|
||||
ttl_seconds: 600
|
||||
```
|
||||
|
||||
Com isso, estas duas chamadas geram a mesma chave:
|
||||
|
||||
```json
|
||||
{
|
||||
"msisdn": "11999999999",
|
||||
"invoice_id": "12345"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"invoice_id": "12345",
|
||||
"msisdn": "11999999999",
|
||||
"session_id": "valor-que-muda",
|
||||
"trace_id": "valor-que-muda"
|
||||
}
|
||||
```
|
||||
|
||||
A chave considera automaticamente apenas `msisdn` e `invoice_id` porque eles estão declarados em `args_schema`. Atributos auxiliares fora do contrato da tool são ignorados.
|
||||
|
||||
Não é necessário declarar `key_fields` no YAML. A fonte da verdade para a chave é o próprio `args_schema` da tool.
|
||||
|
||||
## Configurações globais
|
||||
|
||||
```env
|
||||
ENABLE_MCP_CACHE=true
|
||||
MCP_CACHE_TTL_SECONDS=300
|
||||
TOOLS_CONFIG_PATH=./config/tools.yaml
|
||||
```
|
||||
|
||||
`MCP_CACHE_TTL_SECONDS` é apenas fallback. O TTL preferencial vem de cada tool.
|
||||
|
||||
## Fluxo
|
||||
|
||||
```text
|
||||
AgentRuntimeMixin._call_mcp_tool()
|
||||
↓
|
||||
Lê política cache da tool em tools.yaml
|
||||
↓
|
||||
Monta cache_key por tool_name + campos declarados no args_schema
|
||||
↓
|
||||
cache ausente/false → IC.MCP_CACHE_BYPASS → chama MCP normalmente
|
||||
cache.enabled=true → tenta cache
|
||||
↓
|
||||
cache hit → IC.MCP_CACHE_HIT → retorna resultado salvo
|
||||
cache miss → IC.MCP_CACHE_MISS → chama MCP Router
|
||||
↓
|
||||
ok=true → IC.MCP_CACHE_SET → salva no cache com TTL da tool
|
||||
ok=false → IC.MCP_CACHE_NOT_STORED → não salva
|
||||
```
|
||||
|
||||
## Evidências operacionais
|
||||
|
||||
O runtime grava logs:
|
||||
|
||||
```text
|
||||
MCP cache bypass
|
||||
MCP cache hit
|
||||
MCP cache miss
|
||||
MCP cache set
|
||||
MCP cache not stored
|
||||
```
|
||||
|
||||
O runtime também emite eventos IC. Nos eventos `HIT`, `MISS`, `SET` e `NOT_STORED`, o payload inclui `cache_key` e `cache_key_payload` para auditoria:
|
||||
|
||||
```text
|
||||
IC.MCP_CACHE_BYPASS
|
||||
IC.MCP_CACHE_HIT
|
||||
IC.MCP_CACHE_MISS
|
||||
IC.MCP_CACHE_SET
|
||||
IC.MCP_CACHE_NOT_STORED
|
||||
```
|
||||
|
||||
E eventos de telemetria:
|
||||
|
||||
```text
|
||||
cache.mcp.hit
|
||||
cache.mcp.miss
|
||||
cache.mcp.set
|
||||
```
|
||||
|
||||
## Onde está implementado
|
||||
|
||||
- `src/agent_framework/runtime/agent_runtime.py`
|
||||
- `src/agent_framework/mcp/models.py`
|
||||
- `src/agent_framework/mcp/registry.py`
|
||||
- `config/tools.yaml`
|
||||
|
||||
## Regra de segurança
|
||||
|
||||
O default é `cache.enabled=false`. Isso evita cache acidental em tools mutáveis, como abertura de chamado, troca, devolução, cancelamento ou alteração cadastral.
|
||||
|
||||
## Exemplo de evidência esperada
|
||||
|
||||
Primeira chamada:
|
||||
|
||||
```text
|
||||
IC.MCP_CACHE_MISS tool=consultar_fatura
|
||||
IC.MCP_CACHE_SET tool=consultar_fatura ttl_seconds=600
|
||||
```
|
||||
|
||||
Segunda chamada com os mesmos `msisdn` e `invoice_id`:
|
||||
|
||||
```text
|
||||
IC.MCP_CACHE_HIT tool=consultar_fatura
|
||||
```
|
||||
|
||||
Se a segunda chamada gerar `MISS`, confira o `cache_key_payload`. Ele deve conter os mesmos argumentos efetivos enviados ao MCP.
|
||||
|
||||
|
||||
## Ordem correta dos eventos
|
||||
|
||||
Na primeira chamada cacheável:
|
||||
|
||||
```text
|
||||
IC.MCP_TOOL_REQUESTED
|
||||
IC.MCP_CACHE_MISS
|
||||
IC.MCP_TOOL_EXECUTING
|
||||
IC.MCP_TOOL_EXECUTED
|
||||
IC.MCP_CACHE_SET
|
||||
IC.TOOL_CALLED cached=false
|
||||
```
|
||||
|
||||
Na segunda chamada com os mesmos campos de `args_schema`:
|
||||
|
||||
```text
|
||||
IC.MCP_TOOL_REQUESTED
|
||||
IC.MCP_CACHE_HIT
|
||||
IC.TOOL_CALLED cached=true
|
||||
```
|
||||
|
||||
Quando houver `IC.MCP_CACHE_HIT`, não deve aparecer `IC.MCP_TOOL_EXECUTING` nem `IC.MCP_TOOL_EXECUTED`, porque o MCP Server não foi chamado.
|
||||
175
libs/agent_framework/docs/MCP_PARAMETER_EXTRACTION.md
Normal file
175
libs/agent_framework/docs/MCP_PARAMETER_EXTRACTION.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# MCP Parameter Extraction (`extract`)
|
||||
|
||||
## Objetivo
|
||||
|
||||
O recurso `extract` permite que o framework extraia parâmetros adicionais da mensagem do usuário antes da chamada do MCP Server.
|
||||
|
||||
Esses parâmetros não fazem parte do Business Context (`customer_key`, `contract_key`, `interaction_key`, `account_key`, `resource_key`, `session_key`). Eles representam dados específicos de uma tool MCP.
|
||||
|
||||
Exemplos genéricos:
|
||||
|
||||
- período solicitado;
|
||||
- quantidade solicitada;
|
||||
- código citado pelo usuário;
|
||||
- identificador informado na mensagem;
|
||||
- data textual mencionada;
|
||||
- qualquer entidade de negócio necessária para a tool.
|
||||
|
||||
## Princípio arquitetural
|
||||
|
||||
O mecanismo é declarativo e genérico.
|
||||
|
||||
O framework não deve conhecer nomes de campos específicos, regras de domínio ou valores possíveis. A semântica de cada campo vem exclusivamente da configuração da tool no `mcp_parameter_mapping.yaml`.
|
||||
|
||||
```text
|
||||
identity.yaml
|
||||
→ resolve identidade e chaves canônicas
|
||||
|
||||
mcp_parameter_mapping.yaml
|
||||
→ mapeia parâmetros MCP e declara extrações específicas de tool
|
||||
```
|
||||
|
||||
## Quando usar
|
||||
|
||||
Use `extract` quando:
|
||||
|
||||
1. a informação está presente na mensagem em linguagem natural;
|
||||
2. a informação não é uma chave canônica do Business Context;
|
||||
3. a informação só é necessária para uma tool específica;
|
||||
4. o MCP Server deve receber o valor já estruturado em `args`.
|
||||
|
||||
## Quando não usar
|
||||
|
||||
Não use `extract` para resolver:
|
||||
|
||||
- `customer_key`;
|
||||
- `contract_key`;
|
||||
- `interaction_key`;
|
||||
- `account_key`;
|
||||
- `resource_key`;
|
||||
- `session_key`.
|
||||
|
||||
Essas chaves pertencem ao mecanismo de identidade e devem ser resolvidas por `identity.yaml`.
|
||||
|
||||
## Exemplo de configuração
|
||||
|
||||
```yaml
|
||||
mcp_parameter_mapping:
|
||||
tools:
|
||||
minha_tool:
|
||||
map:
|
||||
customer_key: customer_id
|
||||
contract_key: contract_id
|
||||
session_key: session_id
|
||||
|
||||
extract:
|
||||
parametro_externo:
|
||||
from: message
|
||||
type: string
|
||||
strategy: llm
|
||||
description: >
|
||||
Extraia da mensagem do usuário o valor necessário para preencher
|
||||
parametro_externo. Retorne null quando a informação não estiver
|
||||
presente no texto.
|
||||
```
|
||||
|
||||
## Fluxo de execução
|
||||
|
||||
```text
|
||||
Mensagem do usuário
|
||||
│
|
||||
▼
|
||||
Router identifica intent
|
||||
│
|
||||
▼
|
||||
Framework escolhe a tool MCP
|
||||
│
|
||||
▼
|
||||
MCPParameterMapper aplica map/defaults
|
||||
│
|
||||
▼
|
||||
MCPToolRouter verifica extract da tool escolhida
|
||||
│
|
||||
▼
|
||||
Executor genérico chama LLM para cada extractor declarado
|
||||
│
|
||||
▼
|
||||
Campos extraídos são injetados em args
|
||||
│
|
||||
▼
|
||||
MCP Server é chamado
|
||||
```
|
||||
|
||||
## Exemplo conceitual
|
||||
|
||||
Mensagem do usuário:
|
||||
|
||||
```text
|
||||
Texto em linguagem natural contendo uma informação necessária para a tool.
|
||||
```
|
||||
|
||||
Resultado esperado da extração:
|
||||
|
||||
```json
|
||||
{
|
||||
"parametro_externo": "valor_extraido"
|
||||
}
|
||||
```
|
||||
|
||||
Payload enviado ao MCP:
|
||||
|
||||
```json
|
||||
{
|
||||
"customer_id": "123",
|
||||
"contract_id": "ABC",
|
||||
"session_id": "S1",
|
||||
"parametro_externo": "valor_extraido"
|
||||
}
|
||||
```
|
||||
|
||||
No MCP Server:
|
||||
|
||||
```python
|
||||
valor = args.get("parametro_externo")
|
||||
```
|
||||
|
||||
## Logs esperados
|
||||
|
||||
Quando a extração é executada com sucesso:
|
||||
|
||||
```text
|
||||
mcp.parameter.llm_extracted tool=minha_tool field=parametro_externo value=valor_extraido
|
||||
```
|
||||
|
||||
Quando o valor não é encontrado:
|
||||
|
||||
```text
|
||||
mcp.parameter.llm_extracted_null tool=minha_tool field=parametro_externo
|
||||
```
|
||||
|
||||
Quando o LLM não está disponível ou ocorre erro:
|
||||
|
||||
```text
|
||||
mcp.parameter.llm_extract_failed tool=minha_tool field=parametro_externo error=...
|
||||
```
|
||||
|
||||
## Boas práticas
|
||||
|
||||
- Mantenha `identity.yaml` apenas para identidade e chaves canônicas.
|
||||
- Declare parâmetros adicionais no `mcp_parameter_mapping.yaml`.
|
||||
- Não coloque regras de domínio hardcoded no framework.
|
||||
- Não coloque parsing de linguagem natural dentro do MCP Server.
|
||||
- Nomeie os parâmetros extraídos de forma estável.
|
||||
- Use `description` para orientar o LLM sobre o que deve ser extraído.
|
||||
|
||||
## Regra principal
|
||||
|
||||
O framework deve executar extração somente quando:
|
||||
|
||||
```text
|
||||
1. a tool MCP já foi escolhida;
|
||||
2. essa tool possui extract configurado;
|
||||
3. o extractor usa uma estratégia suportada, como strategy: llm.
|
||||
```
|
||||
|
||||
Sem `extract` declarado, nada é extraído.
|
||||
@@ -0,0 +1,75 @@
|
||||
# OCI Instance Principal and FastMCP support
|
||||
|
||||
This version adds two framework capabilities:
|
||||
|
||||
1. OCI SDK authentication with `OCI_AUTH_MODE=instance_principal`.
|
||||
2. Official MCP/FastMCP client transport in addition to the legacy HTTP mock contract.
|
||||
|
||||
## OCI authentication
|
||||
|
||||
Supported values:
|
||||
|
||||
```env
|
||||
OCI_AUTH_MODE=config_file # local ~/.oci/config profile, default
|
||||
OCI_AUTH_MODE=instance_principal # OCI Compute / OKE workload identity via instance principal
|
||||
OCI_AUTH_MODE=resource_principal # OCI Functions / resource principal contexts
|
||||
```
|
||||
|
||||
For local development, keep:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=oci_openai
|
||||
OCI_GENAI_API_KEY=...
|
||||
```
|
||||
|
||||
For OCI runtimes without API keys, use the SDK provider:
|
||||
|
||||
```env
|
||||
LLM_PROVIDER=oci_sdk
|
||||
OCI_AUTH_MODE=instance_principal
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1...
|
||||
OCI_REGION=sa-saopaulo-1
|
||||
OCI_GENAI_MODEL=cohere.command-r-plus
|
||||
```
|
||||
|
||||
The same `OCI_AUTH_MODE` is also used by the OCI embedding provider:
|
||||
|
||||
```env
|
||||
EMBEDDING_PROVIDER=oci
|
||||
OCI_AUTH_MODE=instance_principal
|
||||
OCI_COMPARTMENT_ID=ocid1.compartment.oc1...
|
||||
OCI_EMBEDDING_MODEL=cohere.embed-multilingual-v3.0
|
||||
```
|
||||
|
||||
> Note: `oci_openai` continues to use the OpenAI-compatible endpoint and API key. Instance principal is implemented through `oci_sdk` because it needs OCI request signing.
|
||||
|
||||
## FastMCP transport
|
||||
|
||||
The previous framework MCP client remains available:
|
||||
|
||||
```yaml
|
||||
servers:
|
||||
telecom:
|
||||
transport: http
|
||||
endpoint: http://localhost:8001/mcp
|
||||
```
|
||||
|
||||
For FastMCP / official MCP Streamable HTTP:
|
||||
|
||||
```yaml
|
||||
servers:
|
||||
telecom:
|
||||
transport: fastmcp
|
||||
endpoint: http://localhost:8001/mcp
|
||||
```
|
||||
|
||||
For MCP SSE:
|
||||
|
||||
```yaml
|
||||
servers:
|
||||
telecom:
|
||||
transport: sse
|
||||
endpoint: http://localhost:8001/sse
|
||||
```
|
||||
|
||||
Tools still use `tools.yaml` and `mcp_parameter_mapping.yaml`. Only the server transport changes.
|
||||
179
libs/agent_framework/docs/README_TIM_OBSERVER_PAYLOAD_FIX.md
Normal file
179
libs/agent_framework/docs/README_TIM_OBSERVER_PAYLOAD_FIX.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Correção TIM Observer Payload / NOC OTel
|
||||
|
||||
Esta versão corrige dois gaps da migração do `agent_framework_oci`:
|
||||
|
||||
1. **Pub/Sub flat**: eventos IC/GRL/analytics passam a ser publicados no contrato flat combinado com Data/TIM, sem envelope `{type, payload}` e sem `payload.payload`.
|
||||
2. **NOC em OpenTelemetry Logs**: eventos NOC passam a ter caminho dedicado para OTel Logs, separado de traces/spans.
|
||||
3. **Sequence automático**: eventos Pub/Sub flat passam a receber `sequence` incremental por `agentId/sessionId`, preservando valor explícito quando já vier no evento.
|
||||
|
||||
## Arquivos alterados/adicionados
|
||||
|
||||
- `src/agent_framework/analytics/tim_payload_mapper.py`
|
||||
- Novo mapper canônico para converter o envelope interno do framework para o payload TIM flat.
|
||||
- Mantém campos canônicos na raiz.
|
||||
- Mantém apenas `agentSpecificData` como objeto aninhado.
|
||||
|
||||
- `src/agent_framework/analytics/providers/pubsub.py`
|
||||
- Publica flat por padrão.
|
||||
- Mantém modo legado por configuração.
|
||||
- Exclui `NOC.*` do Pub/Sub por padrão, seguindo a lib antiga.
|
||||
- Injeta `sequence` automaticamente no payload flat antes do publish.
|
||||
|
||||
- `src/agent_framework/analytics/tim_sequence.py`
|
||||
- Novo gerador de sequence por `agentId/sessionId`.
|
||||
- Suporta Redis `INCR` como contador atômico cross-worker/cross-pod.
|
||||
- Suporta MongoDB com `find_one_and_update` + `$inc`, mantendo paridade com a lib antiga.
|
||||
- Usa fallback em memória quando o backend compartilhado não estiver disponível, sem quebrar o fluxo de observabilidade.
|
||||
- Preserva `sequence` explícito quando o chamador já informou o campo.
|
||||
|
||||
- `src/agent_framework/observability/noc_otel.py`
|
||||
- Novo exportador dedicado de NOC para OpenTelemetry Logs.
|
||||
- Usa `OTLPLogExporter` e `LoggingHandler`.
|
||||
- Aplica DE/PARA flat com `keep_none=True`.
|
||||
- Achata dict/list para string JSON antes de enviar ao OTel.
|
||||
|
||||
- `src/agent_framework/observability/observer.py`
|
||||
- `emit_noc()` agora dispara o canal dedicado de OTel Logs antes da publicação analytics.
|
||||
|
||||
- `src/agent_framework/config/settings.py`
|
||||
- Novas variáveis de configuração.
|
||||
|
||||
## Novas variáveis
|
||||
|
||||
```env
|
||||
# Pub/Sub: padrão corrigido para TIM/Data
|
||||
PUBSUB_PAYLOAD_MODE=flat
|
||||
PUBSUB_EXCLUDE_NOC=true
|
||||
|
||||
# Sequence automático por sessão no payload Pub/Sub flat
|
||||
PUBSUB_SEQUENCE_ENABLED=true
|
||||
|
||||
# auto = Redis se configurado; senão MongoDB se configurado; senão fallback em memória.
|
||||
# Para o BO sem OCI Cache, usar mongodb para manter paridade com a lib antiga.
|
||||
PUBSUB_SEQUENCE_PROVIDER=mongodb
|
||||
|
||||
# Opção Redis, quando existir cache disponível
|
||||
# PUBSUB_SEQUENCE_REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# Opção MongoDB, equivalente ao comportamento antigo via find_one_and_update + $inc
|
||||
PUBSUB_SEQUENCE_MONGODB_URI=mongodb://localhost:27017
|
||||
PUBSUB_SEQUENCE_MONGODB_DATABASE=agent_platform
|
||||
PUBSUB_SEQUENCE_MONGODB_COLLECTION=${AGENT_NAME}_event_counters
|
||||
|
||||
PUBSUB_SEQUENCE_TTL_SECONDS=86400
|
||||
PUBSUB_SEQUENCE_MEMORY_FALLBACK=true
|
||||
PUBSUB_SEQUENCE_KEY_PREFIX=observer:sequence
|
||||
|
||||
# Para voltar temporariamente ao formato antigo envelopado
|
||||
# PUBSUB_PAYLOAD_MODE=legacy
|
||||
|
||||
# NOC via OpenTelemetry Logs
|
||||
ENABLE_NOC_OTEL_LOGS=true
|
||||
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://10.153.35.23/v1/logs
|
||||
OTEL_EXPORTER_OTLP_HOST_HEADER=tim-ai-atend-agnt-opentelemetry
|
||||
OTEL_SERVICE_NAME=ai-agent-template
|
||||
```
|
||||
|
||||
## Exemplo de Pub/Sub corrigido
|
||||
|
||||
```json
|
||||
{
|
||||
"eventType": "IC.FATURA_CONSULTADA",
|
||||
"version": "1.0",
|
||||
"eventDate": "2026-06-19T12:00:00+00:00",
|
||||
"sessionId": "sess-789",
|
||||
"channelId": "whatsapp",
|
||||
"agentId": "billing-agent",
|
||||
"tag": "IC.FATURA_CONSULTADA",
|
||||
"sequence": 12,
|
||||
"agentSpecificData": {
|
||||
"invoiceId": "INV-001"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Observação
|
||||
|
||||
O envelope interno retornado pelo `observer.emit(...)` foi mantido para não quebrar EventBus, Langfuse ou consumidores internos. A correção ocorre no provider Pub/Sub e no novo canal NOC OTel.
|
||||
|
||||
|
||||
## Sequence automático
|
||||
|
||||
No modo flat, o provider Pub/Sub chama `ensure_sequence(message)` antes de publicar.
|
||||
|
||||
Com `sessionId` presente e sem `sequence` explícito, o framework gera:
|
||||
|
||||
```text
|
||||
observer:sequence:<agentId>:<sessionId> -> INCR
|
||||
```
|
||||
|
||||
Exemplo:
|
||||
|
||||
```json
|
||||
{ "eventType": "IC.001", "sessionId": "sess-1", "agentId": "billing", "sequence": 1 }
|
||||
{ "eventType": "IC.002", "sessionId": "sess-1", "agentId": "billing", "sequence": 2 }
|
||||
{ "eventType": "IC.003", "sessionId": "sess-1", "agentId": "billing", "sequence": 3 }
|
||||
```
|
||||
|
||||
Regras:
|
||||
|
||||
- Se `sequence` já vier no metadata/payload, ele é preservado.
|
||||
- Se `sessionId` não existir, o campo não é gerado.
|
||||
- MongoDB é suportado para cenários sem OCI Cache/Redis e usa operação atômica `find_one_and_update` com `$inc`, como na lib antiga.
|
||||
- Redis continua suportado quando houver cache disponível, pois `INCR` é atômico entre workers/pods.
|
||||
- O fallback em memória é apenas best-effort local para ambientes de desenvolvimento ou contingência.
|
||||
|
||||
|
||||
### Sequence com MongoDB
|
||||
|
||||
Para ambientes do BO onde não existe OCI Cache/Redis dimensionado, configure:
|
||||
|
||||
```env
|
||||
PUBSUB_SEQUENCE_ENABLED=true
|
||||
PUBSUB_SEQUENCE_PROVIDER=mongodb
|
||||
PUBSUB_SEQUENCE_MONGODB_URI=mongodb://<host>:27017
|
||||
PUBSUB_SEQUENCE_MONGODB_DATABASE=agent_platform
|
||||
PUBSUB_SEQUENCE_MONGODB_COLLECTION=${AGENT_NAME}_event_counters
|
||||
PUBSUB_SEQUENCE_TTL_SECONDS=86400
|
||||
PUBSUB_SEQUENCE_MEMORY_FALLBACK=true
|
||||
```
|
||||
|
||||
O documento no Mongo usa `_id` igual à chave lógica:
|
||||
|
||||
```text
|
||||
observer:sequence:<agentId>:<sessionId>
|
||||
```
|
||||
|
||||
A atualização é atômica:
|
||||
|
||||
```python
|
||||
find_one_and_update(
|
||||
{"_id": key},
|
||||
{"$inc": {"sequence": 1}},
|
||||
upsert=True,
|
||||
return_document=AFTER,
|
||||
)
|
||||
```
|
||||
|
||||
Também é criado, em best-effort, um índice TTL sobre `expiresAt`. Se o usuário Mongo não tiver permissão para criar índice, a geração de sequence continua funcionando; apenas a limpeza automática pode depender de rotina externa.
|
||||
|
||||
### Collection Mongo compatível com legado
|
||||
|
||||
Quando `PUBSUB_SEQUENCE_PROVIDER=mongodb`, a collection dos contadores pode ser informada explicitamente:
|
||||
|
||||
```env
|
||||
PUBSUB_SEQUENCE_MONGODB_COLLECTION=telecom_contas_event_counters
|
||||
```
|
||||
|
||||
Se essa variável não for definida, o framework usa o padrão legado:
|
||||
|
||||
```text
|
||||
{AGENT_NAME}_event_counters
|
||||
```
|
||||
|
||||
Também são aceitos, por compatibilidade operacional:
|
||||
|
||||
```env
|
||||
MONGODB_EVENT_COUNTERS_COLLECTION=telecom_contas_event_counters
|
||||
EVENT_COUNTERS_COLLECTION=telecom_contas_event_counters
|
||||
```
|
||||
Reference in New Issue
Block a user