bugfix: oci_openai provider
This commit is contained in:
@@ -448,25 +448,69 @@ class ComplianceRail(Guardrail):
|
||||
requer = ctx.get("tipo_fluxo") == "ajuste" or ctx.get("requer_protocolo") is True
|
||||
if not requer:
|
||||
return RailDecision(code=self.code, allowed=True, sanitized_text=None, reason="Compliance Anatel não aplicável", metadata={"calibrated": True})
|
||||
expected = list(ctx.get("expected_protocols") or [])
|
||||
if self._PROTOCOL_PATTERN.search(text or ""):
|
||||
return RailDecision(code=self.code, allowed=True, reason="Resposta contém protocolo obrigatório", metadata={"calibrated": True})
|
||||
patched, missing = self._apply_protocol_fallback(text or "", expected)
|
||||
if patched != (text or ""):
|
||||
|
||||
original = text or ""
|
||||
expected = [str(value).strip() for value in (ctx.get("expected_protocols") or []) if str(value).strip()]
|
||||
|
||||
# Quando o workflow informa os protocolos esperados, esses valores são a
|
||||
# fonte de verdade. Valide-os diretamente no texto (cru ou vocalizado)
|
||||
# antes de recorrer ao regex genérico. Isso evita tanto falso negativo
|
||||
# por distância/Markdown quanto falso positivo por um protocolo diferente.
|
||||
if expected:
|
||||
patched, missing = self._apply_protocol_fallback(original, expected)
|
||||
if not missing:
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="Resposta contém o(s) protocolo(s) esperado(s)",
|
||||
sanitized_text=None,
|
||||
metadata={
|
||||
"expected_protocols": expected,
|
||||
"protocol_validation": "expected_values",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="Resposta sem protocolo obrigatório; protocolo anexado deterministicamente",
|
||||
sanitized_text=patched,
|
||||
metadata={"missing_protocols_spoken": missing, "expected_protocols": expected, "mechanism": "deterministic", "calibrated": True},
|
||||
metadata={
|
||||
"missing_protocols_spoken": missing,
|
||||
"expected_protocols": expected,
|
||||
"protocol_validation": "expected_values",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
|
||||
# Compatibilidade para fluxos legados que exigem protocolo, mas não
|
||||
# fornecem expected_protocols: nesse caso ainda usamos o reconhecimento
|
||||
# genérico por regex.
|
||||
if self._PROTOCOL_PATTERN.search(original):
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="Resposta contém protocolo obrigatório",
|
||||
metadata={
|
||||
"protocol_validation": "generic_regex",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=False,
|
||||
reason="Resposta de ajuste sem número de protocolo",
|
||||
sanitized_text=text,
|
||||
metadata={
|
||||
"expected_protocols": expected, "mechanism": "deterministic", "calibrated": True,
|
||||
"expected_protocols": expected,
|
||||
"protocol_validation": "generic_regex",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
"terminal_action": "retry",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -135,6 +135,7 @@ src/agent_framework/llm/__init__.py
|
||||
src/agent_framework/llm/base.py
|
||||
src/agent_framework/llm/profile_resolver.py
|
||||
src/agent_framework/llm/providers.py
|
||||
src/agent_framework/llm/structured_output.py
|
||||
src/agent_framework/llm/types.py
|
||||
src/agent_framework/mcp/__init__.py
|
||||
src/agent_framework/mcp/client.py
|
||||
|
||||
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.
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 agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
@@ -246,13 +248,4 @@ class GlobalSupervisorRouter:
|
||||
return text.strip()
|
||||
|
||||
def _parse_json(self, raw: Any) -> dict[str, Any]:
|
||||
if isinstance(raw, dict):
|
||||
return raw
|
||||
text = str(raw).strip()
|
||||
if text.startswith("```"):
|
||||
text = re.sub(r"^```(?:json)?", "", text).strip()
|
||||
text = re.sub(r"```$", "", text).strip()
|
||||
match = re.search(r"\{.*\}", text, flags=re.S)
|
||||
if match:
|
||||
text = match.group(0)
|
||||
return json.loads(text)
|
||||
return parse_json_object(raw)
|
||||
|
||||
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.
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.
@@ -42,6 +42,8 @@ Uso via função standalone (compatibilidade):
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -239,10 +241,10 @@ def classify_confirmation(
|
||||
return False, f"invoke_error — fallback pessimista: {exc}"
|
||||
|
||||
try:
|
||||
payload: dict[str, Any] = json.loads(raw)
|
||||
except (json.JSONDecodeError, TypeError) as exc:
|
||||
payload: dict[str, Any] = parse_json_object(raw)
|
||||
except (ValueError, TypeError) as exc:
|
||||
logger.warning(
|
||||
"confirmation_rail.json_parse_failed raw=%r error=%r — fallback pessimista",
|
||||
"confirmation_rail.structured_parse_failed raw=%r error=%r — fallback pessimista",
|
||||
raw[:200],
|
||||
exc,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,8 @@ Contexto de migração:
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -89,7 +91,7 @@ class RagsecRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"ragsec_rail.invoke_error session=%s exc=%r — assuming allowed",
|
||||
|
||||
@@ -15,6 +15,8 @@ Contexto de migração:
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -88,7 +90,7 @@ class RevprecRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"revprec_rail.invoke_error session=%s exc=%r — assuming allowed",
|
||||
|
||||
@@ -10,6 +10,8 @@ Implementa o Protocol ``Rail`` de contracts.py (AT-06.2).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -133,7 +135,7 @@ class CorrespondenciaItemRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"correspondencia_item_rail.invoke_error session=%s exc=%r — assuming no violation",
|
||||
|
||||
@@ -8,6 +8,8 @@ Implementa o Protocol ``Rail`` de contracts.py (AT-06.4).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -127,7 +129,7 @@ class GroundednessRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"groundedness_rail.invoke_error session=%s exc=%r — assuming no violation",
|
||||
|
||||
@@ -10,6 +10,8 @@ Implementa o Protocol ``Rail`` de contracts.py (AT-06.1).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -131,7 +133,7 @@ class IntencaoCancelarRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"intencao_cancelar_rail.invoke_error session=%s exc=%r — assuming no violation",
|
||||
|
||||
@@ -10,6 +10,8 @@ Implementa o Protocol ``Rail`` de contracts.py (AT-06.3).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -135,7 +137,7 @@ class QuantidadeCoerente:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"quantidade_coerente_rail.invoke_error session=%s exc=%r — assuming no violation",
|
||||
|
||||
@@ -11,6 +11,8 @@ Implementa o Protocol ``Rail`` de contracts.py (AT-06.6).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -131,7 +133,7 @@ class ServicoCorrretoRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"servico_correto_rail.invoke_error session=%s exc=%r — assuming no violation",
|
||||
|
||||
@@ -11,6 +11,8 @@ Implementa o Protocol ``Rail`` de contracts.py (AT-06.5).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -128,7 +130,7 @@ class VerbalizacaoPrematura:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"verbalizacao_prematura_rail.invoke_error session=%s exc=%r — assuming no violation",
|
||||
|
||||
@@ -22,6 +22,8 @@ Fallback conservador:
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -155,7 +157,7 @@ class ToxRail:
|
||||
|
||||
try:
|
||||
raw = self._client.invoke(self.code, input_vars)
|
||||
result: dict = json.loads(raw) if isinstance(raw, str) else raw
|
||||
result: dict = parse_json_object(raw) if isinstance(raw, str) else raw
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"tox_rail.invoke_error session=%s exc=%r — assuming allowed",
|
||||
|
||||
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 agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -85,15 +87,9 @@ def _extract_text(raw: Any) -> str:
|
||||
|
||||
def _parse_json(text: str) -> dict[str, Any]:
|
||||
try:
|
||||
return json.loads(text)
|
||||
return parse_json_object(text)
|
||||
except Exception:
|
||||
match = re.search(r"\{[\s\S]*\}", text or "")
|
||||
if match:
|
||||
try:
|
||||
return json.loads(match.group(0))
|
||||
except Exception:
|
||||
pass
|
||||
return {"allowed": False, "label": "ERROR", "reason": (text or "")[:500]}
|
||||
return {"allowed": False, "label": "ERROR", "reason": (text or "")[:500]}
|
||||
|
||||
|
||||
def _first_substring_match(text: str, triggers: tuple[str, ...]) -> str | None:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_framework.llm.structured_output import parse_json_object
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
@@ -102,16 +104,4 @@ def _safe_context(context: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _parse_json(raw: Any) -> dict[str, Any]:
|
||||
text = str(raw or "").strip()
|
||||
if text.startswith("```"):
|
||||
text = text.strip("`")
|
||||
if text.lower().startswith("json"):
|
||||
text = text[4:].strip()
|
||||
start = text.find("{")
|
||||
end = text.rfind("}")
|
||||
if start >= 0 and end >= start:
|
||||
text = text[start:end + 1]
|
||||
data = json.loads(text)
|
||||
if not isinstance(data, dict):
|
||||
raise ValueError("LLM guardrail returned non-object JSON")
|
||||
return data
|
||||
return parse_json_object(raw)
|
||||
|
||||
@@ -448,25 +448,69 @@ class ComplianceRail(Guardrail):
|
||||
requer = ctx.get("tipo_fluxo") == "ajuste" or ctx.get("requer_protocolo") is True
|
||||
if not requer:
|
||||
return RailDecision(code=self.code, allowed=True, sanitized_text=None, reason="Compliance Anatel não aplicável", metadata={"calibrated": True})
|
||||
expected = list(ctx.get("expected_protocols") or [])
|
||||
if self._PROTOCOL_PATTERN.search(text or ""):
|
||||
return RailDecision(code=self.code, allowed=True, reason="Resposta contém protocolo obrigatório", metadata={"calibrated": True})
|
||||
patched, missing = self._apply_protocol_fallback(text or "", expected)
|
||||
if patched != (text or ""):
|
||||
|
||||
original = text or ""
|
||||
expected = [str(value).strip() for value in (ctx.get("expected_protocols") or []) if str(value).strip()]
|
||||
|
||||
# Quando o workflow informa os protocolos esperados, esses valores são a
|
||||
# fonte de verdade. Valide-os diretamente no texto (cru ou vocalizado)
|
||||
# antes de recorrer ao regex genérico. Isso evita tanto falso negativo
|
||||
# por distância/Markdown quanto falso positivo por um protocolo diferente.
|
||||
if expected:
|
||||
patched, missing = self._apply_protocol_fallback(original, expected)
|
||||
if not missing:
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="Resposta contém o(s) protocolo(s) esperado(s)",
|
||||
sanitized_text=None,
|
||||
metadata={
|
||||
"expected_protocols": expected,
|
||||
"protocol_validation": "expected_values",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="Resposta sem protocolo obrigatório; protocolo anexado deterministicamente",
|
||||
sanitized_text=patched,
|
||||
metadata={"missing_protocols_spoken": missing, "expected_protocols": expected, "mechanism": "deterministic", "calibrated": True},
|
||||
metadata={
|
||||
"missing_protocols_spoken": missing,
|
||||
"expected_protocols": expected,
|
||||
"protocol_validation": "expected_values",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
|
||||
# Compatibilidade para fluxos legados que exigem protocolo, mas não
|
||||
# fornecem expected_protocols: nesse caso ainda usamos o reconhecimento
|
||||
# genérico por regex.
|
||||
if self._PROTOCOL_PATTERN.search(original):
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=True,
|
||||
reason="Resposta contém protocolo obrigatório",
|
||||
metadata={
|
||||
"protocol_validation": "generic_regex",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
},
|
||||
)
|
||||
|
||||
return RailDecision(
|
||||
code=self.code,
|
||||
allowed=False,
|
||||
reason="Resposta de ajuste sem número de protocolo",
|
||||
sanitized_text=text,
|
||||
metadata={
|
||||
"expected_protocols": expected, "mechanism": "deterministic", "calibrated": True,
|
||||
"expected_protocols": expected,
|
||||
"protocol_validation": "generic_regex",
|
||||
"mechanism": "deterministic",
|
||||
"calibrated": True,
|
||||
"terminal_action": "retry",
|
||||
},
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user