Files
Lamont/tests/unit/test_parser.py
Chelsea Lee fbdf33e894
Some checks failed
CI / test (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled
Build reusable bot framework
2026-07-19 21:53:24 -05:00

276 lines
9.7 KiB
Python

from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
import pytest
from ai import parser
from core.registry import FrameworkRegistry
def _response(content=None, reasoning=None, choices=True):
message = SimpleNamespace(content=content, reasoning=reasoning)
return SimpleNamespace(choices=[SimpleNamespace(message=message)] if choices else [])
def test_template_rendering_preserves_literal_json_braces():
template = (
'Schema: {"action":"create","payload":{"items":[1, 2]}}\n'
"Input: {user_input}\nUnknown: {leave_this_alone}"
)
rendered = parser._render_template(template, {"user_input": "remind me"})
assert '{"action":"create","payload":{"items":[1, 2]}}' in rendered
assert "Input: remind me" in rendered
assert "Unknown: {leave_this_alone}" in rendered
def test_json_extraction_handles_fenced_text_and_nested_values():
response = """```json
Here is the structured result:
{"action":"create","payload":{"items":[{"id":1},{"id":2}]}}
```"""
assert parser._extract_json_from_text(response) == {
"action": "create",
"payload": {"items": [{"id": 1}, {"id": 2}]},
}
def test_parse_retries_invalid_json_and_validator_failures(monkeypatch):
responses = iter(
[
"not JSON",
'{"valid":false}',
'```json\n{"valid":true,"nested":{"value":7}}\n```',
]
)
prompts = []
def fake_llm(_system_prompt, user_prompt):
prompts.append(user_prompt)
return next(responses)
prompt = {
"system": "Return an object",
"user_template": (
'Literal schema: {"valid":true}\nUser message: {user_input}'
),
}
validator = lambda value: [] if value.get("valid") else ["valid must be true"]
monkeypatch.setitem(parser.AI_CONFIG["validation"], "max_retries", 3)
monkeypatch.setattr(parser, "_call_llm", fake_llm)
result = parser.parse(
"test input",
"focused",
prompt_override=prompt,
validator=validator,
)
assert result == {"valid": True, "nested": {"value": 7}}
assert len(prompts) == 3
assert 'Literal schema: {"valid":true}' in prompts[0]
assert "Response was not valid JSON" in prompts[1]
assert "valid must be true" in prompts[2]
@pytest.mark.asyncio
async def test_command_parser_routes_then_uses_focused_module_prompt(monkeypatch):
target = FrameworkRegistry()
target.begin_module("reminders", "modules.reminders")
target.register_command(
"reminder",
lambda _context, _parsed: None,
{
"system": "Focused reminder parser",
"user_template": (
'Timezone: {timezone}\nMessage: {user_input}\n'
'Schema: {"action":"create"}'
),
},
validator=lambda value: (
[] if value.get("action") == "create" else ["invalid action"]
),
description="Create and manage reminders",
)
target.finish_module()
llm = AsyncMock(
side_effect=[
'{"interaction_type":"reminder","confidence":0.99}',
(
'```json\n{"action":"create","message":"call home",'
'"payload":{"source":"dm"}}\n```'
),
]
)
monkeypatch.setattr(parser, "_call_llm_async", llm)
result = await parser.parse_command_async(
"remind me to call home",
target,
history=[("hello", {"interaction_type": "reminder"})],
timezone_name="America/Chicago",
)
assert result == {
"action": "create",
"message": "call home",
"payload": {"source": "dm"},
"interaction_type": "reminder",
}
assert llm.await_count == 2
route_system, route_user = llm.await_args_list[0].args
focused_system, focused_user = llm.await_args_list[1].args
assert route_system == parser.AI_CONFIG["prompts"]["command_parser"]["system"]
assert "- reminder: Create and manage reminders" in route_user
assert focused_system == "Focused reminder parser"
assert "Timezone: America/Chicago" in focused_user
assert 'Schema: {"action":"create"}' in focused_user
def test_clients_are_created_lazily_and_cached(monkeypatch):
syncClient = SimpleNamespace()
asyncClient = SimpleNamespace()
syncFactory = MagicMock(return_value=syncClient)
asyncFactory = MagicMock(return_value=asyncClient)
monkeypatch.setattr(parser, "OpenAI", syncFactory)
monkeypatch.setattr(parser, "AsyncOpenAI", asyncFactory)
monkeypatch.setattr(parser, "_sync_client", None)
monkeypatch.setattr(parser, "_async_client", None)
monkeypatch.setenv("OPENROUTER_API_KEY", "provider-key")
assert parser._get_client() is syncClient
assert parser._get_client() is syncClient
assert parser._get_client(async_client=True) is asyncClient
assert parser._get_client(async_client=True) is asyncClient
assert syncFactory.call_count == 1
assert asyncFactory.call_count == 1
def test_response_text_and_request_arguments(monkeypatch):
assert parser._response_text(_response(" result ")) == "result"
assert parser._response_text(_response(reasoning=" reason ")) == "reason"
assert parser._response_text(_response(choices=False)) is None
monkeypatch.setitem(parser.AI_CONFIG, "json_mode", True)
arguments = parser._request_args("system", "user")
assert arguments["messages"] == [
{"role": "system", "content": "system"},
{"role": "user", "content": "user"},
]
assert arguments["response_format"] == {"type": "json_object"}
def test_sync_llm_call_returns_content_and_absorbs_provider_failure(monkeypatch):
completions = MagicMock()
completions.create.side_effect = [
_response('{"ok":true}'),
RuntimeError("provider unavailable"),
]
client = SimpleNamespace(chat=SimpleNamespace(completions=completions))
monkeypatch.setattr(parser, "_get_client", lambda **_kwargs: client)
assert parser._call_llm("system", "user") == '{"ok":true}'
assert parser._call_llm("system", "user") is None
@pytest.mark.asyncio
async def test_async_llm_call_returns_content_and_absorbs_failure(monkeypatch):
create = AsyncMock(
side_effect=[_response('{"ok":true}'), RuntimeError("offline")]
)
client = SimpleNamespace(
chat=SimpleNamespace(completions=SimpleNamespace(create=create))
)
monkeypatch.setattr(parser, "_get_client", lambda **_kwargs: client)
assert await parser._call_llm_async("system", "user") == '{"ok":true}'
assert await parser._call_llm_async("system", "user") is None
def test_parse_reports_unknown_unavailable_and_exhausted_results(monkeypatch):
assert parser.parse("hello", "missing") == {
"error": "Unknown interaction type: missing"
}
prompt = {"system": "system", "user_template": "{user_input}"}
monkeypatch.setattr(parser, "_call_llm", lambda *_args: None)
assert parser.parse("hello", "test", prompt_override=prompt) == {
"error": "AI service unavailable",
"user_input": "hello",
}
monkeypatch.setitem(parser.AI_CONFIG["validation"], "max_retries", 2)
monkeypatch.setattr(parser, "_call_llm", lambda *_args: "not json")
exhausted = parser.parse("hello", "test", prompt_override=prompt)
assert exhausted["error"] == "Failed to parse after 2 attempts"
assert exhausted["validation_errors"] == ["Response was not valid JSON"]
@pytest.mark.asyncio
async def test_async_parse_unknown_unavailable_and_exhausted(monkeypatch):
assert await parser.parse_async("hello", "missing") == {
"error": "Unknown interaction type: missing"
}
prompt = {"system": "system", "user_template": "{user_input}"}
call = AsyncMock(return_value=None)
monkeypatch.setattr(parser, "_call_llm_async", call)
assert await parser.parse_async("hello", "test", prompt_override=prompt) == {
"error": "AI service unavailable",
"user_input": "hello",
}
monkeypatch.setitem(parser.AI_CONFIG["validation"], "max_retries", 1)
call.return_value = "[]"
exhausted = await parser.parse_async(
"hello", "test", prompt_override=prompt
)
assert exhausted["validation_errors"] == [
"Response must be a JSON object"
]
@pytest.mark.asyncio
async def test_command_router_handles_early_and_ambiguous_results(monkeypatch):
target = FrameworkRegistry()
target.begin_module("one", "modules.one")
target.register_command(
"one",
lambda *_args: None,
{"system": "system", "user_template": "{user_input}"},
)
target.finish_module()
call = AsyncMock(return_value={"error": "offline"})
monkeypatch.setattr(parser, "parse_async", call)
assert await parser.parse_command_async("hello", target) == {
"error": "offline"
}
call.return_value = {
"needs_clarification": "which one?",
"confidence": 0.95,
}
assert await parser.parse_command_async("hello", target) == {
"needs_clarification": "which one?",
"confidence": 0.95,
}
call.return_value = {"interaction_type": "one", "confidence": 0.2}
ambiguous = await parser.parse_command_async("hello", target)
assert "needs_clarification" in ambiguous
call.return_value = {"interaction_type": "missing", "confidence": 1.0}
assert await parser.parse_command_async("hello", target) == {
"error": "Unknown command type: missing"
}
def test_registered_validator_is_used(monkeypatch):
prompt = {"system": "system", "user_template": "{user_input}"}
parser.register_validator("registered", lambda value: [] if value["ok"] else ["bad"])
monkeypatch.setattr(parser, "_call_llm", lambda *_args: '{"ok":true}')
assert parser.parse("hello", "registered", prompt_override=prompt) == {"ok": True}