wren applies all/every commands itself, answers temperatures, reads aliases and number words; tts says units and clock times
wren's model answers "turn off all lights" with one call, and at 0.98 a
living room light switched on. The node now applies such a command to every
wired target of the kind. Number words become digits ("in five minutes"
lost its delay), aliases put the house's words and known mishearings in the
model's, and sensors.temperature is answered from <area>_temperature or
outdoor. tts writes out units and clock times before speaking.
This commit is contained in:
@@ -41,9 +41,11 @@ class FakeWren:
|
||||
def __init__(self) -> None:
|
||||
self.answer: tuple[list[Any], float] = ([], 1.0)
|
||||
self.houses: list[_House] = []
|
||||
self.commands: list[str] = []
|
||||
|
||||
def act(self, command: str, house: _House) -> tuple[list[Any], float]:
|
||||
self.houses.append(house)
|
||||
self.commands.append(command)
|
||||
return self.answer
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Any
|
||||
|
||||
from fluksio.ai import client
|
||||
from fluksio.flow.messages import DType, MessageSpec
|
||||
from fluksio.flow.nodes.speech import SttNode, TtsNode, sentences
|
||||
from fluksio.flow.nodes.speech import SttNode, TtsNode, sentences, speakable
|
||||
from fluksio.flow.pipeline import Pipeline
|
||||
|
||||
URL = "http://speech:8100"
|
||||
@@ -40,6 +40,15 @@ def test_sentences_are_cut_after_their_ending():
|
||||
]
|
||||
|
||||
|
||||
def test_units_and_clock_times_are_said_as_words():
|
||||
said = speakable("22 °C and 21.5° at 07:30, 3.4 kWh, 505 W, 2 kW, 7:05 or 18:00.")
|
||||
assert said == (
|
||||
"22 degrees and 21.5 degrees at 7 30, 3.4 kilowatt hours, 505 watts, "
|
||||
"2 kilowatts, 7 oh 5 or 18 o'clock."
|
||||
)
|
||||
assert speakable("Kitchen light on, 3 Watts.") == "Kitchen light on, 3 Watts."
|
||||
|
||||
|
||||
def test_an_utterance_is_one_session_committed_when_listening_ends(monkeypatch):
|
||||
heard: list[bytes] = []
|
||||
|
||||
|
||||
@@ -216,3 +216,85 @@ def test_a_call_on_an_area_the_command_does_not_name_is_not_applied(fake_wren):
|
||||
assert out is not None
|
||||
assert "house.bathroom_light" not in out
|
||||
assert [a["applied"] for a in out["house.actions"]] == [False]
|
||||
|
||||
|
||||
def _speaking(*targets: tuple[str, DType], inputs=(), **params: object) -> WrenNode:
|
||||
node = WrenNode(
|
||||
requires=[MessageSpec(name="command", port="prompt", dtype=DType.STR), *inputs],
|
||||
provides=[
|
||||
*(MessageSpec(name=port, dtype=dtype) for port, dtype in targets),
|
||||
MessageSpec(name="reply", dtype=DType.STR),
|
||||
MessageSpec(name="unhandled", dtype=DType.STR),
|
||||
],
|
||||
params={"apply": True, **params},
|
||||
)
|
||||
node.assign_flow("house", "control")
|
||||
return node
|
||||
|
||||
|
||||
def test_the_house_words_and_number_words_reach_the_model_as_its_own(fake_wren):
|
||||
node = _speaking(("patio_light", DType.BOOL), aliases={"Page Too": "patio"})
|
||||
fake_wren.answer = ([], 0.99)
|
||||
|
||||
out = node.execute({"house.command": "Turn on the page too light in five minutes."})
|
||||
|
||||
assert fake_wren.commands == ["turn on the patio light in 5 minutes"]
|
||||
# The assistant reads the house's words too, as they were said otherwise.
|
||||
assert out == {
|
||||
"house.unhandled": "Turn on the patio light in five minutes.",
|
||||
"house.reply": CANT,
|
||||
}
|
||||
|
||||
|
||||
def test_a_command_for_all_of_a_kind_reaches_every_wired_one(fake_wren):
|
||||
lights = [(f"{area}_light", DType.BOOL) for area in ("kitchen", "office")]
|
||||
node = _speaking(*lights, ("office_shutter", DType.STR))
|
||||
|
||||
out = node.execute({"house.command": "Turn off all lights."})
|
||||
assert out == {
|
||||
"house.kitchen_light": False,
|
||||
"house.office_light": False,
|
||||
"house.reply": "All lights off.",
|
||||
}
|
||||
out = node.execute({"house.command": "turn everything off in the office"})
|
||||
assert out == {
|
||||
"house.office_light": False,
|
||||
"house.reply": "Lights off in the office.",
|
||||
}
|
||||
assert fake_wren.commands == [] # the node's, not the model's
|
||||
|
||||
# A question or an exception is the model's again.
|
||||
fake_wren.answer = ([], 0.99)
|
||||
node.execute({"house.command": "are all lights off"})
|
||||
node.execute({"house.command": "turn off all lights but the kitchen"})
|
||||
assert len(fake_wren.commands) == 2
|
||||
|
||||
|
||||
def test_a_question_for_a_temperature_is_answered_from_its_reading(fake_wren):
|
||||
node = _speaking(
|
||||
inputs=[
|
||||
MessageSpec(name="outside", port="outdoor", dtype=DType.FLOAT),
|
||||
MessageSpec(
|
||||
name="kitchen_temp", port="kitchen_temperature", dtype=DType.FLOAT
|
||||
),
|
||||
]
|
||||
)
|
||||
assert node.readings == {"kitchen": "kitchen_temperature"}
|
||||
readings = {"house.outside": 7.6, "house.kitchen_temp": 21.2}
|
||||
|
||||
for area, said in [("garden", "outside"), ("kitchen", "in the kitchen")]:
|
||||
fake_wren.answer = ([FakeCall("sensors.temperature", {"area": area})], 0.99)
|
||||
command = f"what is the temperature in the {area}"
|
||||
out = node.execute({"house.command": command, **readings})
|
||||
degrees = 8 if area == "garden" else 21
|
||||
assert out == {"house.reply": f"It's {degrees} degrees {said}."}
|
||||
|
||||
# No reading for the bedroom, or a model unsure of it: the assistant's.
|
||||
for area, confidence in [("bedroom", 0.99), ("kitchen", 0.5)]:
|
||||
fake_wren.answer = (
|
||||
[FakeCall("sensors.temperature", {"area": area})],
|
||||
confidence,
|
||||
)
|
||||
command = f"how warm is it in the {area}"
|
||||
out = node.execute({"house.command": command, **readings})
|
||||
assert out == {"house.unhandled": command, "house.reply": CANT}
|
||||
|
||||
Reference in New Issue
Block a user