mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-09-07 10:13:46 +00:00
New features: Domain_Requested_LLM_Composition, Domain_Requested_RAG, Offline_Workflow_Regression, Pause_Resume_Workflow, Voice_Interruption_Replay, Workflow_Error_Recovery, Durable Idempotency, Workflow_Pause_Resume, Dynamic_Transaction_States, Post_Finalization_Replay, Retrieval_Tool_Guardrails
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
# Domain Requested LLM Composition
|
||||
|
||||
## Objetivo
|
||||
|
||||
Permitir que uma tool/workflow de domínio informe que o resultado operacional não deve ser devolvido diretamente ao usuário e precisa ser redigido pelo LLM oficial do agente, sem criar um gateway LLM dentro do domínio.
|
||||
|
||||
## Contrato
|
||||
|
||||
A tool pode devolver, em qualquer nível do resultado:
|
||||
|
||||
```json
|
||||
{
|
||||
"requires_llm_composition": true,
|
||||
"response_instruction": "Explique ao cliente a forma de devolução usando apenas os dados do workflow."
|
||||
}
|
||||
```
|
||||
|
||||
Também é aceito `response_instructions` como lista.
|
||||
|
||||
O `AgentRuntimeMixin` percorre recursivamente o resultado MCP. Quando a flag está ativa, `build_direct_mcp_answer()` retorna `None`; a resposta segue pelo LLM configurado no framework e recebe as evidências MCP no contexto normal do agente.
|
||||
|
||||
## Por que isso existe
|
||||
|
||||
Algumas operações, como pró-rata, possuem resultado determinístico e efeitos já concluídos, mas precisam de linguagem natural adequada ao canal. Antes, o domínio Contas possuía um gateway LLM próprio. Agora o domínio só declara a necessidade e a instrução; execução, credenciais, profiles, tracing e custos continuam no `agent_framework_oci`.
|
||||
|
||||
## Regras
|
||||
|
||||
- Não usar para decidir se uma transação deve ocorrer.
|
||||
- Não usar para inventar valores ou protocolos.
|
||||
- A instrução deve exigir que o LLM use somente as evidências retornadas pela tool/workflow.
|
||||
- Pode coexistir com `requires_rag`; nesse caso o framework também executa RAG antes da composição.
|
||||
51
Tuning-Performance/Domain_Requested_RAG/README.md
Normal file
51
Tuning-Performance/Domain_Requested_RAG/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Domain-Requested RAG
|
||||
|
||||
## Objetivo
|
||||
|
||||
Permitir que uma tool/workflow de domínio declare que o resultado MCP **não é suficiente** para produzir a resposta final e que o agente deve recuperar conhecimento usando o `RagService` oficial do `agent_framework_oci`.
|
||||
|
||||
O domínio não instancia banco vetorial, embeddings, LLM ou cliente RAG. Ele apenas devolve no resultado:
|
||||
|
||||
```json
|
||||
{
|
||||
"requires_rag": true,
|
||||
"rag_queries": [
|
||||
"Como cancelar o serviço Paramount+ no parceiro? Procedimento oficial de cancelamento."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Fluxo
|
||||
|
||||
```text
|
||||
Workflow/tool de domínio
|
||||
|
|
||||
| requires_rag + rag_queries
|
||||
v
|
||||
AgentRuntimeMixin
|
||||
|
|
||||
+-- impede direct MCP answer
|
||||
+-- ignora SKIP_RAG_WHEN_MCP_SUFFICIENT para este resultado
|
||||
+-- usa rag_query/rag_queries como query override
|
||||
v
|
||||
RagService
|
||||
v
|
||||
Vector/Graph store configurado no framework
|
||||
v
|
||||
LLM do agente com MCP evidence + RAG evidence
|
||||
```
|
||||
|
||||
## Regras
|
||||
|
||||
1. `requires_rag=false` ou ausente mantém o comportamento padrão.
|
||||
2. `SKIP_RAG_WHEN_MCP_SUFFICIENT=true` continua válido para tools normais.
|
||||
3. Quando `requires_rag=true`, o framework não usa `build_direct_mcp_answer()`.
|
||||
4. `rag_query` aceita uma query; `rag_queries` aceita várias queries, preservadas e deduplicadas em ordem.
|
||||
5. O domínio nunca executa `RagService` diretamente.
|
||||
6. Guardrails de retrieval (`RAGSEC`, `RET_REL` etc.) continuam executando normalmente sobre o contexto recuperado.
|
||||
|
||||
## Exemplo Contas
|
||||
|
||||
No fluxo VAS Estratégico, após o cliente rejeitar a explicação e pedir cancelamento, a action devolve queries específicas por parceiro. O `VasAgent` usa o `RagService` do framework para recuperar o procedimento oficial e compor a resposta.
|
||||
|
||||
Isso substitui o comportamento legado em que o backend Contas possuía uma busca RAG própria dentro de `vas_strategic()`.
|
||||
21
Tuning-Performance/Offline_Workflow_Regression/README.md
Normal file
21
Tuning-Performance/Offline_Workflow_Regression/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Offline Workflow Regression
|
||||
|
||||
O backend de produção de `WorkflowRuntime` continua sendo **LangGraph**. A ausência do pacote `langgraph` em produção é erro de configuração.
|
||||
|
||||
Para builders restritos/offline, o runtime aceita `allow_deterministic_fallback=True`. Esse modo é deliberadamente opt-in e existe somente para exercitar a DSL do framework (actions, edges, condições, pause/resume e trace) de forma reproduzível em testes offline/regressão. Quando `allow_deterministic_fallback=True`, o backend determinístico é selecionado explicitamente mesmo que LangGraph esteja instalado. Ele nunca é selecionado automaticamente em produção.
|
||||
|
||||
Exemplo de teste:
|
||||
|
||||
```python
|
||||
runtime = WorkflowRuntime(
|
||||
repository,
|
||||
actions=registry,
|
||||
allow_deterministic_fallback=True,
|
||||
)
|
||||
first = await runtime.arun("workflow", payload)
|
||||
assert first.status == "PAUSED"
|
||||
final = await runtime.aresume("workflow", first.execution_id, {"resposta": "SIM"})
|
||||
assert final.status == "COMPLETED"
|
||||
```
|
||||
|
||||
O objetivo é não transformar indisponibilidade de rede/PyPI em `pytest.skip`, sem mascarar o requisito de LangGraph do runtime de produção.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Implementação no framework
|
||||
|
||||
A evolução adiciona `WorkflowPause` e `WorkflowExpectedInput` ao modelo de workflow e mantém o LangGraph como detalhe interno de `WorkflowRuntime`.
|
||||
|
||||
O runtime aceita condições declarativas `all`, `any`, `not`, `eq`, `neq`, `exists`, `path/equals`, `path/not_equals` e `path/in`. Em um nó com `pause`, a action é executada primeiro e a interrupção ocorre em um nó técnico separado. Na retomada, `langgraph.types.Command(resume=...)` injeta o valor esperado e segue para `resume_from` sem repetir a action que precedeu o pause.
|
||||
|
||||
`FrameworkStateGraph` é a facade para aplicações que ainda precisam compor um grafo de agentes; templates oficiais devem usar a facade e não importar LangGraph diretamente.
|
||||
|
||||
## Preservação de estado em falhas posteriores
|
||||
|
||||
O `WorkflowRuntime` também preserva o último snapshot persistido do LangGraph quando uma action posterior falha. O resultado `FAILED` contém `output`, `state` e `trace` dos nodes que já terminaram com sucesso.
|
||||
|
||||
Isso é necessário para workflows transacionais: por exemplo, se um protocolo foi criado e uma chamada posterior falha, o chamador ainda recebe o `protocol_number` persistido e pode executar recuperação/idempotência sem repetir o primeiro side effect.
|
||||
|
||||
O runtime não transforma falha em sucesso e não reexecuta automaticamente a action; ele apenas preserva a evidência durável já existente no checkpointer.
|
||||
34
Tuning-Performance/Pause_Resume_Workflow/README.md
Normal file
34
Tuning-Performance/Pause_Resume_Workflow/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Pause/Resume Workflow — LangGraph encapsulado pelo Agent Framework OCI
|
||||
|
||||
Este exemplo demonstra uma capability genérica do `agent_framework_oci`: workflows determinísticos podem interromper a execução para obter uma resposta do cliente e retomar posteriormente pelo mesmo `execution_id`, sem que o domínio importe ou monte um `StateGraph`.
|
||||
|
||||
## Conceito
|
||||
|
||||
A aplicação declara o workflow em YAML. `WorkflowRuntime` transforma a definição em LangGraph internamente, utiliza o checkpointer configurado pelo framework e expõe somente:
|
||||
|
||||
- `arun(name, payload)` — inicia ou executa o workflow;
|
||||
- `aresume(name, execution_id, value)` — retoma o workflow pausado;
|
||||
- `WorkflowRunResult.status` — `PAUSED`, `COMPLETED` ou `FAILED`.
|
||||
|
||||
O `pause` é compilado em um nó separado da action anterior. Isto impede que uma action com efeito colateral seja executada novamente quando o cliente responde.
|
||||
|
||||
## Executar
|
||||
|
||||
A partir da raiz do exemplo, com as dependências do framework instaladas:
|
||||
|
||||
```bash
|
||||
python -m app.demo
|
||||
pytest -q
|
||||
```
|
||||
|
||||
## YAML
|
||||
|
||||
`workflows/confirmacao.v1.yaml` mostra `expected_input`, normalização, valores permitidos e `resume_from`.
|
||||
|
||||
## Persistência
|
||||
|
||||
O exemplo usa `MemorySaver` apenas para ser autocontido. Em aplicações reais use `create_langgraph_checkpointer(settings)`. Assim `execution_id` é o `thread_id` do LangGraph e a retomada sobrevive a processos/replicas conforme o provider configurado (por exemplo Autonomous Database).
|
||||
|
||||
## Regra arquitetural
|
||||
|
||||
Código de domínio não deve importar `langgraph.graph.StateGraph`. Para grafos de agentes use `FrameworkStateGraph`; para workflows determinísticos de negócio use `WorkflowRuntime`.
|
||||
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework.workflows import FileWorkflowRepository, WorkflowActionRegistry, WorkflowRuntime
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def build_runtime(*, offline_test_fallback: bool = False) -> WorkflowRuntime:
|
||||
actions = WorkflowActionRegistry()
|
||||
|
||||
async def preparar(params, state):
|
||||
return {"assunto": params.get("assunto") or "operação"}
|
||||
|
||||
async def perguntar(params, state):
|
||||
return {"mensagem": f"Deseja confirmar {params['assunto']}?"}
|
||||
|
||||
async def decidir(params, state):
|
||||
return {
|
||||
"mensagem": "Operação confirmada." if params["resposta"] == "SIM" else "Operação cancelada.",
|
||||
"confirmado": params["resposta"] == "SIM",
|
||||
}
|
||||
|
||||
actions.register("preparar_operacao", preparar)
|
||||
actions.register("montar_pergunta", perguntar)
|
||||
actions.register("registrar_decisao", decidir)
|
||||
|
||||
checkpointer = None
|
||||
if not offline_test_fallback:
|
||||
# Produção/exemplo real continua usando LangGraph + checkpointer. O import
|
||||
# fica aqui para que a regressão offline do repositório não dependa de rede.
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
checkpointer = MemorySaver()
|
||||
|
||||
return WorkflowRuntime(
|
||||
FileWorkflowRepository(ROOT / "workflows"),
|
||||
actions=actions,
|
||||
checkpointer=checkpointer,
|
||||
allow_deterministic_fallback=offline_test_fallback,
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
runtime = build_runtime()
|
||||
first = await runtime.arun("confirmacao", {"assunto": "a alteração do plano"})
|
||||
print(first.model_dump(mode="json"))
|
||||
assert first.status == "PAUSED"
|
||||
resumed = await runtime.aresume("confirmacao", first.execution_id, {"resposta_usuario": "sim"})
|
||||
print(resumed.model_dump(mode="json"))
|
||||
assert resumed.status == "COMPLETED"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from app.demo import build_runtime
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pause_resume_does_not_repeat_previous_action():
|
||||
# Regressão offline: exercita a mesma DSL/WorkflowRuntime sem exigir download
|
||||
# de LangGraph no builder. Produção continua usando build_runtime() default.
|
||||
runtime = build_runtime(offline_test_fallback=True)
|
||||
first = await runtime.arun("confirmacao", {"assunto": "o cancelamento"})
|
||||
assert first.status == "PAUSED"
|
||||
assert first.pause["expected_input"]["key"] == "resposta_usuario"
|
||||
before = [item for item in first.trace if item.get("action") == "preparar_operacao"]
|
||||
assert len(before) == 1
|
||||
resumed = await runtime.aresume("confirmacao", first.execution_id, {"resposta_usuario": "SIM"})
|
||||
assert resumed.status == "COMPLETED"
|
||||
after = [item for item in resumed.trace if item.get("action") == "preparar_operacao"]
|
||||
assert len(after) == 1
|
||||
assert resumed.state["vars"]["decidir"]["confirmado"] is True
|
||||
@@ -0,0 +1 @@
|
||||
version: 1
|
||||
@@ -0,0 +1,32 @@
|
||||
name: confirmacao
|
||||
version: 1
|
||||
start: preparar
|
||||
nodes:
|
||||
- id: preparar
|
||||
action: preparar_operacao
|
||||
input:
|
||||
assunto: $.input.assunto
|
||||
- id: perguntar
|
||||
action: montar_pergunta
|
||||
input:
|
||||
assunto: $.vars.preparar.assunto
|
||||
pause:
|
||||
enabled: true
|
||||
return_from: $.output.mensagem
|
||||
expected_input:
|
||||
key: resposta_usuario
|
||||
allowed_values: [SIM, NAO]
|
||||
normalize: upper_strip
|
||||
resume_from: decidir
|
||||
- id: decidir
|
||||
action: registrar_decisao
|
||||
input:
|
||||
resposta: $.input.resposta_usuario
|
||||
assunto: $.vars.preparar.assunto
|
||||
edges:
|
||||
- from: preparar
|
||||
to: perguntar
|
||||
- from: perguntar
|
||||
to: END
|
||||
- from: decidir
|
||||
to: END
|
||||
47
Tuning-Performance/Voice_Interruption_Replay/README.md
Normal file
47
Tuning-Performance/Voice_Interruption_Replay/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Voice Interruption / Replay — framework-native
|
||||
|
||||
Esta melhoria move para `agent_framework.channels.interruption` comportamentos que antes costumavam ser implementados dentro de agentes de voz específicos.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Evitar que `idle_nudge`, barge-in e fala residual pós-finalização reabram desnecessariamente o LangGraph principal, executem tools novamente ou confundam uma resposta de continuidade com uma nova intenção.
|
||||
|
||||
## Ordem de decisão
|
||||
|
||||
1. **Sessão encerrada**: replay da última fala terminal (ou fallback), preservando `terminal_status`. Não chama LangGraph, tools ou guardrails.
|
||||
2. **Idle nudge**: replay da última fala real do assistente. Não chama LangGraph, tools ou guardrails.
|
||||
3. **Fala não interrompível**: replay literal.
|
||||
4. **Fala interrompível com contexto anterior**: executa `processing_interruption_classifier` pelo `LLMProvider` do framework.
|
||||
- `1`: reprocessa o complemento;
|
||||
- `0`, erro ou resposta inválida: replay fail-safe.
|
||||
5. **Sem fala anterior suficiente**: processa normalmente.
|
||||
|
||||
## Classificador
|
||||
|
||||
O classificador usa o profile `processing_interruption_classifier` e solicita resposta binária `1/0`. Ele não possui gateway LLM próprio e não depende do domínio do agente.
|
||||
|
||||
## Telemetria
|
||||
|
||||
O backend emite `channel.processing_interruption.classified` com `regenerate=true|false`. Replays retornam metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"replay": true,
|
||||
"replay_reason": "post_finalize|idle_nudge|non_interruptible_speech|classifier_result_0",
|
||||
"framework_short_circuit": true,
|
||||
"llm_called": false,
|
||||
"tools_called": false,
|
||||
"guardrails_called": false
|
||||
}
|
||||
```
|
||||
|
||||
No caso `classifier_result_0`, o LLM chamado é apenas o classificador leve; o LangGraph conversacional e o LLM do agente não são executados.
|
||||
|
||||
## Templates
|
||||
|
||||
A funcionalidade foi aplicada em:
|
||||
|
||||
- `templates/agent_template_backend/app/main.py`
|
||||
- `templates/agent_template_backend_day_zero/app/main.py`
|
||||
|
||||
Portanto novos agentes herdam o comportamento sem copiar código de domínio.
|
||||
16
Tuning-Performance/Workflow_Error_Recovery/README.md
Normal file
16
Tuning-Performance/Workflow_Error_Recovery/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Workflow Error Recovery
|
||||
|
||||
O `WorkflowRuntime` preserva o último snapshot válido do LangGraph e, nesta versão, também expõe `error_details` estruturado quando a exceção externa oferece campos como `status_code`, `body` e `attempts`.
|
||||
|
||||
Isso permite que o domínio diferencie erro técnico de erro de negócio sem acoplar o framework ao provider. O framework continua responsável por runtime/checkpoint/trace; o domínio interpreta apenas o contrato do seu provider.
|
||||
|
||||
Exemplo conceitual:
|
||||
|
||||
```python
|
||||
result = await runtime.arun("workflow_transacional", payload)
|
||||
if result.status == "FAILED":
|
||||
print(result.output) # nodes concluídos antes da falha
|
||||
print(result.trace) # trace parcial
|
||||
print(result.error) # mensagem humana/técnica
|
||||
print(result.error_details) # status/body/attempts se disponíveis
|
||||
```
|
||||
Reference in New Issue
Block a user