File size: 6,154 Bytes
7ab5aee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import unittest
from app.agents.browser_decision import _fallback_browser_decision
from app.agents.browser_tools import (
_normalize_points,
execute_browser_tool_call,
parse_browser_json_response,
)
from app.agents.graph.state import AgentState
from app.agents.llm_client import _parse_openrouter_responses_api
from app.agents.tooling import ToolCall, tool
@tool(description="Add two numbers together")
def add_numbers(left: int, right: int, note: str = "") -> dict:
return {"sum": left + right, "note": note}
class ToolingTests(unittest.TestCase):
def test_tool_schema_marks_required_and_optional_fields(self):
schema = add_numbers.schema
self.assertEqual(schema.name, "add_numbers")
self.assertEqual(schema.parameters["required"], ["left", "right"])
self.assertEqual(schema.parameters["properties"]["note"]["type"], "string")
self.assertEqual(schema.parameters["properties"]["left"]["type"], "integer")
def test_execute_browser_tool_call_normalizes_search_decision(self):
result = execute_browser_tool_call(
ToolCall(
id="call_search",
name="search_web",
arguments={
"query": "latest groq models",
"reason": "current page lacks the answer",
"known_facts": ["Groq exposes an OpenAI-compatible API"],
},
)
)
self.assertEqual(result["action"], "SEARCH")
self.assertEqual(result["value"], "latest groq models")
self.assertEqual(result["reason"], "current page lacks the answer")
self.assertEqual(result["known_facts"], ["Groq exposes an OpenAI-compatible API"])
def test_parse_browser_json_response_maps_legacy_fields(self):
parsed = parse_browser_json_response(
"""
{
"action": "navigate",
"url": "https://example.com/page",
"reason": "This looks like the source page",
"missing_points": ["exact launch date"]
}
"""
)
self.assertEqual(parsed["action"], "NAVIGATE")
self.assertEqual(parsed["value"], "https://example.com/page")
self.assertEqual(parsed["reason"], "This looks like the source page")
self.assertEqual(parsed["missing_points"], ["exact launch date"])
def test_parse_browser_json_response_rejects_scroll_when_mode_disallows_it(self):
with self.assertRaises(ValueError) as ctx:
parse_browser_json_response('{"action":"scroll"}', allow_scroll=False)
self.assertIn("SCROLL", str(ctx.exception))
def test_normalize_points_treats_plain_string_as_single_item(self):
self.assertEqual(
_normalize_points("trump age still missing"),
["trump age still missing"],
)
def test_agent_state_update_research_progress_does_not_split_string_into_chars(self):
state = AgentState(task="qual idade do trump?")
state.update_research_progress(missing_points="trump age still missing")
self.assertEqual(state.missing_points, ["trump age still missing"])
def test_fallback_browser_decision_does_not_leak_reasoning_as_done(self):
decision = _fallback_browser_decision(
task="qual idade do trump?",
current_url="https://en.wikipedia.org/wiki/Donald_Trump",
blocked=False,
allow_scroll=True,
links=[],
raw_text="We are on the Wikipedia page and should think step by step...",
)
self.assertNotEqual(decision["action"], "DONE")
self.assertEqual(decision["action"], "SCROLL")
def test_fallback_browser_decision_uses_unseen_link_when_available(self):
decision = _fallback_browser_decision(
task="qual idade do trump?",
current_url="https://html.duckduckgo.com/html/?q=qual+idade+do+trump",
blocked=False,
allow_scroll=False,
links=["https://en.wikipedia.org/wiki/Donald_Trump"],
raw_text="Let us think carefully first...",
)
self.assertEqual(decision["action"], "NAVIGATE")
self.assertEqual(decision["value"], "https://en.wikipedia.org/wiki/Donald_Trump")
def test_parse_openrouter_responses_api_separates_reasoning_from_content_and_tools(self):
parsed = _parse_openrouter_responses_api(
{
"model": "nvidia/nemotron-3-super-120b-a12b:free",
"status": "completed",
"output": [
{
"type": "reasoning",
"summary": [
"Need the current age",
"Should inspect the canonical page first",
],
},
{
"type": "function_call",
"id": "fc_1",
"call_id": "call_1",
"name": "navigate_to_url",
"arguments": "{\"url\":\"https://en.wikipedia.org/wiki/Donald_Trump\"}",
},
{
"type": "message",
"role": "assistant",
"content": [
{
"type": "output_text",
"text": "Vou abrir a página principal.",
}
],
},
],
}
)
self.assertEqual(parsed.reasoning, [
"Need the current age",
"Should inspect the canonical page first",
])
self.assertEqual(parsed.content, "Vou abrir a página principal.")
self.assertEqual(len(parsed.tool_calls), 1)
self.assertEqual(parsed.tool_calls[0].name, "navigate_to_url")
self.assertEqual(
parsed.tool_calls[0].arguments,
{"url": "https://en.wikipedia.org/wiki/Donald_Trump"},
)
if __name__ == "__main__":
unittest.main()
|