bugfix: oci_openai provider

This commit is contained in:
2026-08-27 20:34:15 -03:00
parent fc64c6ddd7
commit 0ecff719b7
329 changed files with 864 additions and 144 deletions

View File

@@ -0,0 +1,60 @@
import pytest
from agent_framework.guardrails.rails import ComplianceRail
@pytest.mark.asyncio
async def test_cmp_accepts_expected_protocol_directly_even_when_regex_misses_markdown_distance():
rail = ComplianceRail()
text = (
"[FaturasAgent] Entendido. Seu aceite foi registrado e o protocolo de atendimento "
"foi aberto com o número **1234567890**."
)
decision = await rail.evaluate(
text,
{
"tipo_fluxo": "ajuste",
"expected_protocols": ["1234567890"],
},
)
assert decision.allowed is True
assert decision.sanitized_text is None
assert decision.metadata["protocol_validation"] == "expected_values"
assert decision.metadata["expected_protocols"] == ["1234567890"]
@pytest.mark.asyncio
async def test_cmp_does_not_accept_a_different_protocol_when_expected_value_is_known():
rail = ComplianceRail()
text = "Seu protocolo é 9999999999."
decision = await rail.evaluate(
text,
{
"requer_protocolo": True,
"expected_protocols": ["1234567890"],
},
)
assert decision.allowed is True
assert decision.sanitized_text is not None
assert "um dois três quatro cinco seis sete oito nove zero" in decision.sanitized_text
assert decision.metadata["missing_protocols_spoken"] == [
"um dois três quatro cinco seis sete oito nove zero"
]
assert decision.metadata["protocol_validation"] == "expected_values"
@pytest.mark.asyncio
async def test_cmp_keeps_regex_compatibility_when_expected_protocols_are_unavailable():
rail = ComplianceRail()
decision = await rail.evaluate(
"Seu protocolo é 1234567890.",
{"requer_protocolo": True},
)
assert decision.allowed is True
assert decision.metadata["protocol_validation"] == "generic_regex"

View File

@@ -0,0 +1,62 @@
import pytest
from agent_framework.llm.structured_output import (
StructuredOutputError,
parse_json_object,
parse_structured_output,
)
def test_strict_json_object():
assert parse_json_object('{"decision":"KEEP","confidence":0.9}') == {
"decision": "KEEP",
"confidence": 0.9,
}
def test_single_quotes_python_literal():
assert parse_json_object("{'decision': 'KEEP', 'confidence': 0.9, 'ok': True, 'reason': None}") == {
"decision": "KEEP",
"confidence": 0.9,
"ok": True,
"reason": None,
}
def test_prose_around_json():
raw = 'Resultado da análise:\n{"decision":"ROUTE","confidence":0.8}\nFim.'
assert parse_json_object(raw)["decision"] == "ROUTE"
def test_prose_around_single_quoted_mapping():
raw = "Vou retornar o objeto pedido: {'decision': 'HANDOFF', 'confidence': 0.7} texto final"
assert parse_json_object(raw)["decision"] == "HANDOFF"
def test_markdown_fence():
raw = "```json\n{\"allowed\": true, \"reason\": \"OK\"}\n```"
assert parse_json_object(raw) == {"allowed": True, "reason": "OK"}
def test_braces_inside_string_do_not_break_extraction():
raw = "prefix {'reason': 'cliente disse {nao}', 'allowed': True} suffix"
assert parse_json_object(raw)["reason"] == "cliente disse {nao}"
def test_apostrophe_inside_double_quoted_json_is_preserved():
raw = 'texto {"name":"D\'Ávila","allowed":true} fim'
assert parse_json_object(raw)["name"] == "D'Ávila"
def test_array_supported_by_generic_parser():
assert parse_structured_output("prefix [1, 2, 3] suffix", expected_type=list) == [1, 2, 3]
def test_object_wrapper_rejects_array():
with pytest.raises(StructuredOutputError):
parse_json_object("[1,2,3]")
def test_does_not_execute_arbitrary_python():
with pytest.raises(StructuredOutputError):
parse_json_object("{'x': __import__('os').system('echo unsafe')}")

View File

@@ -384,7 +384,7 @@ def test_agent_state_declares_durable_transaction_latch():
import importlib.util
from pathlib import Path
state_path = Path(__file__).parents[2] / "app" / "state.py"
state_path = Path(__file__).parents[1] / "templates" / "agent_template_backend" / "app" / "state.py"
spec = importlib.util.spec_from_file_location("contas_agent_state_v10", state_path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader

View File

@@ -3,7 +3,13 @@ from types import SimpleNamespace
import pytest
from agent_framework.llm.base import LLMProvider
from agent_framework.llm.providers import MockLLMProvider, OCISDKProvider, _extract_reasoning_content
from agent_framework.llm.providers import (
MockLLMProvider,
OCISDKProvider,
_extract_reasoning_content,
_extract_openai_message_content,
_extract_finish_reason,
)
from agent_framework.llm.types import LLMResponse
@@ -64,3 +70,37 @@ def test_oci_sdk_reasoning_is_extracted_from_choice_message():
response = SimpleNamespace(data=SimpleNamespace(chat_response=chat_response))
assert OCISDKProvider._extract_reasoning_content(response) == "oci reasoning"
def test_openai_compatible_plain_string_content_is_preserved():
message = SimpleNamespace(content='{"decision":"KEEP"}')
assert _extract_openai_message_content(message) == '{"decision":"KEEP"}'
def test_openai_compatible_list_content_is_joined():
message = SimpleNamespace(content=[
SimpleNamespace(text='{"decision":'),
{"text": '"KEEP"}'},
])
assert _extract_openai_message_content(message) == '{"decision":"KEEP"}'
def test_openai_compatible_dict_message_and_content_are_supported():
message = {"content": [{"text": "{\"ok\":true}"}]}
assert _extract_openai_message_content(message) == '{"ok":true}'
def test_openai_compatible_unknown_content_fails_closed_to_empty_string():
message = SimpleNamespace(content=object())
assert _extract_openai_message_content(message) == ""
def test_openai_compatible_reasoning_is_not_used_as_answer_fallback():
message = SimpleNamespace(content=None, reasoning_content='{"decision":"KEEP"}')
assert _extract_openai_message_content(message) == ""
assert _extract_reasoning_content(message) == '{"decision":"KEEP"}'
def test_finish_reason_is_extracted_from_object_and_dict():
assert _extract_finish_reason(SimpleNamespace(finish_reason="length")) == "length"
assert _extract_finish_reason({"finish_reason": "stop"}) == "stop"

View File

@@ -89,6 +89,9 @@ async def test_continue_bypasses_router_without_regex_rules():
assert decision.method == "continuity"
assert decision.metadata["route_bypassed"] is True
assert llm.calls[0][1]["profile_name"] == "route_continuity"
# LLM tuning belongs to llm_profiles.yaml; continuity must not override it.
assert "max_tokens" not in llm.calls[0][1]
assert "temperature" not in llm.calls[0][1]
prompt = json.loads(llm.calls[0][0][1]["content"])
assert prompt["current_message"] == "o que está incluso?"
assert "product_agent" not in prompt["other_agents"]