Orqanta-AI / tests /test_tools.py
buggandhi's picture
Initial release: Orqanta AI 121M edge foundation model weights, datasets and tools
43a90a1 verified
Raw History Blame Contribute Delete
7.47 kB
import enum
import sys
import typing
import pytest
from needle import tool, Field
from needle.agent.tools import build_schema, pydantic_schema, _is_pydantic_model
pep604 = pytest.mark.skipif(sys.version_info < (3, 10),
reason="PEP 604 unions need Python 3.10+")
def test_basic_schema_types_and_required():
@tool
def add(a: int, b: int) -> int:
"Add two numbers."
return a + b
schema = add._needle_tool
assert schema["name"] == "add"
assert schema["description"] == "Add two numbers."
assert schema["parameters"]["properties"]["a"] == {"type": "integer"}
assert set(schema["parameters"]["required"]) == {"a", "b"}
def test_defaults_are_optional():
def f(city: str, units: str = "metric"):
pass
schema = build_schema(f)
assert schema["parameters"]["required"] == ["city"]
assert "units" in schema["parameters"]["properties"]
def test_literal_list_and_dict_types():
def f(mode: typing.Literal["fast", "slow"], items: list, meta: dict):
pass
props = build_schema(f)["parameters"]["properties"]
assert props["mode"] == {"type": "string", "enum": ["fast", "slow"]}
assert props["items"]["type"] == "array"
assert props["meta"] == {"type": "object"}
def test_typed_list_items():
def f(tags: typing.List[int]):
pass
props = build_schema(f)["parameters"]["properties"]
assert props["tags"] == {"type": "array", "items": {"type": "integer"}}
def test_optional_annotation_not_required():
def f(a: str, b: typing.Optional[int] = None):
pass
schema = build_schema(f)
assert schema["parameters"]["required"] == ["a"]
def test_optional_annotated_field_constraints():
def f(
value: typing.Optional[typing.Annotated[
str, Field(pattern="^[a-z]+$", min_length=2, format="email")]] = None,
count: typing.Annotated[typing.Optional[int], Field(ge=0)] = None,
):
pass
schema = build_schema(f)
properties = schema["parameters"]["properties"]
assert properties["value"] == {
"type": "string",
"pattern": "^[a-z]+$",
"minLength": 2,
"format": "email",
}
assert properties["count"] == {"type": "integer", "minimum": 0}
assert not {"value", "count"} & set(schema["parameters"].get("required", []))
@pep604
def test_pep604_none_union_not_required():
def f(a: str, b: str | None = None, c: int | None = None, d: str | int | None = None):
pass
schema = build_schema(f)
assert schema["parameters"]["required"] == ["a"]
props = schema["parameters"]["properties"]
assert props["b"] == {"type": "string"}
assert props["c"] == {"type": "integer"}
assert props["d"] == {"type": "string"}
@pep604
def test_pep604_union_matches_typing_union():
def f(value: int | str):
pass
def g(value: typing.Union[int, str]):
pass
assert build_schema(f)["parameters"] == build_schema(g)["parameters"]
assert build_schema(f)["parameters"]["properties"]["value"] == {"type": "integer"}
def test_field_constraints_and_docstring_args():
def f(temp: int = Field(description="temperature", ge=0, le=100),
name: str = Field(default="x", pattern="^[a-z]+$", min_length=1)):
"""Set the thing.
Args:
temp: the temperature in C
"""
schema = build_schema(f)
props = schema["parameters"]["properties"]
assert props["temp"]["minimum"] == 0 and props["temp"]["maximum"] == 100
assert props["temp"]["description"] == "temperature"
assert props["name"]["pattern"] == "^[a-z]+$"
assert props["name"]["minLength"] == 1
assert "temp" in schema["parameters"]["required"]
assert "name" not in schema["parameters"].get("required", [])
def test_docstring_description_falls_back_to_args():
def f(city: str):
"""Look up weather.
Args:
city: the city to look up
"""
props = build_schema(f)["parameters"]["properties"]
assert props["city"]["description"] == "the city to look up"
def test_docstring_arg_description_keeps_its_wrapped_lines():
# A Google-style description that wraps continues on lines indented under
# its argument. Only the first line reached the schema, so the constraint
# on the second never reached the model.
def f(time: str, label: str = ""):
"""Set an alarm.
Args:
time: The alarm time in 24-hour HH:MM form, for example 07:30
or 18:45. Never a relative time like "in ten minutes".
label (str): A short name
shown when it rings.
Returns:
dict: The alarm that was set.
"""
props = build_schema(f)["parameters"]["properties"]
assert props["time"]["description"] == (
'The alarm time in 24-hour HH:MM form, for example 07:30 '
'or 18:45. Never a relative time like "in ten minutes".')
assert props["label"]["description"] == "A short name shown when it rings."
# The Returns section is not arguments: an entry there that shares a
# parameter's name overwrote the parameter's description.
def g(count: int):
"""Count things.
Args:
count: How many to count.
Returns:
count: How many were counted.
"""
props = build_schema(g)["parameters"]["properties"]
assert props["count"]["description"] == "How many to count."
def test_pydantic_model_schema():
import pydantic
class Weather(pydantic.BaseModel):
"Weather query."
city: str
units: str = "metric"
assert _is_pydantic_model(Weather)
schema = pydantic_schema(Weather)
assert schema["name"] == "Weather"
assert "city" in schema["parameters"]["properties"]
assert schema["parameters"]["required"] == ["city"]
assert schema["description"] == "Weather query."
def test_tool_decorator_preserves_callable():
@tool
def greet(name: str) -> str:
"Greet someone."
return "hi " + name
assert greet("bob") == "hi bob"
assert greet._needle_tool["name"] == "greet"
def test_numeric_literal_and_int_enum_schema():
class Level(enum.IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
def f(
port: typing.Literal[80, 443, 8080],
level: Level,
mode: typing.Literal["fast", "slow"] = "fast"
):
pass
props = build_schema(f)["parameters"]["properties"]
assert props["port"] == {"type": "integer", "enum": [80, 443, 8080]}
assert props["level"] == {"type": "integer", "enum": [1, 2, 3]}
assert props["mode"] == {"type": "string", "enum": ["fast", "slow"]}
def test_annotated_optional_is_not_required():
def f(city: str, units: typing.Annotated[typing.Optional[str], Field(description="metric or imperial")]):
pass
def g(city: str, units: typing.Optional[str]):
pass
assert build_schema(f)["parameters"]["required"] == ["city"]
assert build_schema(f)["parameters"]["required"] == build_schema(g)["parameters"]["required"]
assert build_schema(f)["parameters"]["properties"]["units"]["description"] == "metric or imperial"
@pep604
def test_annotated_pep604_optional_is_not_required():
def f(city: str, units: typing.Annotated[str | None, Field(description="metric or imperial")]):
pass
assert build_schema(f)["parameters"]["required"] == ["city"]