mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-09-07 18:23:46 +00:00
bugfix: oci_openai provider
This commit is contained in:
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.
60
tests/test_compliance_protocol_expected_values.py
Normal file
60
tests/test_compliance_protocol_expected_values.py
Normal 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"
|
||||
62
tests/test_structured_output_parser.py
Normal file
62
tests/test_structured_output_parser.py
Normal 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')}")
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/unit/__pycache__/test_cache.cpython-313-pytest-9.0.2.pyc
Normal file
BIN
tests/unit/__pycache__/test_cache.cpython-313-pytest-9.0.2.pyc
Normal file
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.
BIN
tests/unit/__pycache__/test_rag.cpython-313-pytest-9.0.2.pyc
Normal file
BIN
tests/unit/__pycache__/test_rag.cpython-313-pytest-9.0.2.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/unit/__pycache__/test_sse.cpython-313-pytest-9.0.2.pyc
Normal file
BIN
tests/unit/__pycache__/test_sse.cpython-313-pytest-9.0.2.pyc
Normal file
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.
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
Reference in New Issue
Block a user