171 lines
5.5 KiB
Python
171 lines
5.5 KiB
Python
import datetime
|
|
import logging
|
|
|
|
import pytest
|
|
|
|
from src import models
|
|
from src.deriver.deriver import (
|
|
_format_messages_for_prompt, # pyright: ignore[reportPrivateUsage]
|
|
)
|
|
from src.utils.representation import (
|
|
DeductiveObservation,
|
|
ExplicitObservation,
|
|
ExplicitObservationBase,
|
|
PromptRepresentation,
|
|
Representation,
|
|
)
|
|
|
|
|
|
def test_prompt_representation_schema_orders_citations_before_content() -> None:
|
|
schema = PromptRepresentation.model_json_schema()
|
|
explicit_properties = schema["$defs"]["ExplicitObservationBase"]["properties"]
|
|
|
|
assert list(explicit_properties) == ["source_indices", "content"]
|
|
|
|
|
|
def test_representation_is_empty_and_diff():
|
|
"""is_empty and diff_representation behave per the new definitions."""
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
shared_time = now - datetime.timedelta(seconds=10)
|
|
exp_shared_1 = ExplicitObservation(
|
|
content="A",
|
|
created_at=shared_time,
|
|
message_ids=[1],
|
|
session_name="s",
|
|
)
|
|
exp_shared_2 = ExplicitObservation(
|
|
content="B",
|
|
created_at=shared_time,
|
|
message_ids=[1],
|
|
session_name="s",
|
|
)
|
|
rep1 = Representation(explicit=[exp_shared_1], deductive=[])
|
|
rep2 = Representation(
|
|
explicit=[
|
|
ExplicitObservation(
|
|
content="A",
|
|
created_at=shared_time,
|
|
message_ids=[1],
|
|
session_name="s",
|
|
),
|
|
exp_shared_2,
|
|
]
|
|
)
|
|
|
|
assert not rep1.is_empty()
|
|
assert Representation().is_empty()
|
|
|
|
diff = rep1.diff_representation(rep2)
|
|
assert [e.content for e in diff.explicit] == ["B"]
|
|
assert diff.deductive == []
|
|
|
|
|
|
def test_representation_formatting_methods():
|
|
"""__str__ and format_as_markdown produce expected section headers and content."""
|
|
now = datetime.datetime.now(datetime.timezone.utc)
|
|
e = ExplicitObservation(
|
|
content="has a dog",
|
|
created_at=now,
|
|
message_ids=[1],
|
|
session_name="s",
|
|
)
|
|
d = DeductiveObservation(
|
|
created_at=now,
|
|
message_ids=[1],
|
|
session_name="s",
|
|
conclusion="owns a pet",
|
|
premises=[e.content],
|
|
)
|
|
rep = Representation(explicit=[e], deductive=[d])
|
|
|
|
s = str(rep)
|
|
assert "EXPLICIT:" in s
|
|
assert "DEDUCTIVE:" in s
|
|
assert "owns a pet" in s
|
|
|
|
md = rep.format_as_markdown()
|
|
assert "## Explicit Observations" in md
|
|
assert "## Deductive Observations" in md
|
|
assert "owns a pet" in md
|
|
assert "Premises:" in md
|
|
|
|
|
|
def test_prompt_representation_conversion():
|
|
"""PromptRepresentation.to_representation maps strings to observation objects.
|
|
|
|
Note: In the current architecture, the Deriver only creates explicit observations.
|
|
Deductive and inductive observations are created by the Dreamer agent.
|
|
Therefore, from_prompt_representation only converts explicit observations.
|
|
"""
|
|
pr = PromptRepresentation(
|
|
explicit=[ExplicitObservationBase(content="A")],
|
|
# Deductive observations in PromptRepresentation are ignored by from_prompt_representation
|
|
# because the Deriver only produces explicit observations
|
|
# deductive=[
|
|
# DeductiveObservationBase(
|
|
# conclusion="C", premises=["P1"], source_ids=["id1"]
|
|
# )
|
|
# ],
|
|
)
|
|
timestamp = datetime.datetime(2025, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
|
rep = Representation.from_prompt_representation(
|
|
pr,
|
|
message_ids=[1],
|
|
prompt_message_ids=[1],
|
|
session_name="s",
|
|
created_at=timestamp,
|
|
)
|
|
assert isinstance(rep, Representation)
|
|
assert [e.content for e in rep.explicit] == ["A"]
|
|
# Deductive observations from PromptRepresentation are not converted
|
|
# (they would be created directly by the Dreamer via the create_observations tool)
|
|
assert len(rep.deductive) == 0
|
|
assert rep.explicit[0].created_at == timestamp
|
|
|
|
|
|
def test_mixed_peer_source_indices_resolve_against_prompt_order(
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
created_at = datetime.datetime(2025, 1, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
|
messages = [
|
|
models.Message(id=10, peer_name="bob", content="Which option?"),
|
|
models.Message(id=20, peer_name="alice", content="The first one"),
|
|
models.Message(id=30, peer_name="bob", content="Got it"),
|
|
]
|
|
for message in messages:
|
|
message.created_at = created_at
|
|
|
|
formatted_messages, prompt_message_ids = _format_messages_for_prompt(messages)
|
|
prompt_representation = PromptRepresentation(
|
|
explicit=[
|
|
ExplicitObservationBase(
|
|
content="Alice chose the first option",
|
|
source_indices=[0, 1, 3],
|
|
)
|
|
]
|
|
)
|
|
|
|
with caplog.at_level(logging.WARNING, logger="src.utils.representation"):
|
|
representation = Representation.from_prompt_representation(
|
|
prompt_representation,
|
|
message_ids=[20],
|
|
prompt_message_ids=prompt_message_ids,
|
|
session_name="s",
|
|
created_at=created_at,
|
|
)
|
|
|
|
observation = representation.explicit[0]
|
|
assert observation.source_indices == [0, 1]
|
|
assert observation.source_message_ids == [10, 20]
|
|
assert [
|
|
(line[:3], message_id, line.split(": ", 1)[1])
|
|
for message_id, line in zip(
|
|
prompt_message_ids, formatted_messages.splitlines(), strict=True
|
|
)
|
|
] == [
|
|
("[0]", 10, "Which option?"),
|
|
("[1]", 20, "The first one"),
|
|
("[2]", 30, "Got it"),
|
|
]
|
|
assert "Dropping out-of-range source_indices [3]" in caplog.text
|