mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
975fb4714c
The provider-neutral typed Turn (flat, role-discriminated; uniform tuple[ContentBlock] content + .text; AttachmentRef by-reference content; ToolCall raw-arg str; ProviderNative producer-tagged opaque lane; TurnMeta sidecar). In-memory foundation for the wire-shape narrow waist — no consumers yet; storage deserialization, the lowering layer, and the provider translators wire onto it in subsequent steps.
80 lines
2.5 KiB
Python
80 lines
2.5 KiB
Python
"""Unit tests for the canonical ``Turn`` model (turnstone.core.trajectory)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from turnstone.core.trajectory import (
|
|
AttachmentRef,
|
|
ProviderNative,
|
|
Role,
|
|
TextBlock,
|
|
ToolCall,
|
|
Turn,
|
|
TurnMeta,
|
|
)
|
|
|
|
|
|
def test_role_values() -> None:
|
|
assert {r.value for r in Role} == {"user", "assistant", "tool", "system"}
|
|
# Constructible from the wire string (used by the row→Turn adapter).
|
|
assert Role("assistant") is Role.ASSISTANT
|
|
|
|
|
|
def test_text_joins_only_textblocks() -> None:
|
|
turn = Turn(
|
|
Role.USER,
|
|
(TextBlock("look at "), AttachmentRef("sha-1", "image"), TextBlock("this")),
|
|
)
|
|
# Attachment blocks contribute nothing to the FTS/text projection.
|
|
assert turn.text == "look at this"
|
|
|
|
|
|
def test_user_helper() -> None:
|
|
turn = Turn.user("hello")
|
|
assert turn.role is Role.USER
|
|
assert turn.content == (TextBlock("hello"),)
|
|
assert turn.text == "hello"
|
|
|
|
|
|
def test_tool_helper_carries_error_flag() -> None:
|
|
ok = Turn.tool("call_1", "result")
|
|
err = Turn.tool("call_2", "boom", is_error=True)
|
|
assert ok.role is Role.TOOL and ok.tool_call_id == "call_1" and ok.is_error is False
|
|
assert err.is_error is True and err.text == "boom"
|
|
|
|
|
|
def test_assistant_with_tool_calls_and_native() -> None:
|
|
tc = ToolCall(id="call_1", name="get_weather", arguments='{"city": "Paris"}')
|
|
native = ProviderNative(producer="anthropic", blocks=({"type": "thinking"},))
|
|
turn = Turn.assistant("on it", tool_calls=(tc,), native=native)
|
|
assert turn.role is Role.ASSISTANT
|
|
assert turn.tool_calls == (tc,)
|
|
assert turn.native is native
|
|
assert turn.text == "on it"
|
|
|
|
|
|
def test_assistant_empty_text_has_no_content_block() -> None:
|
|
# A tool-only assistant turn carries no TextBlock.
|
|
turn = Turn.assistant("", tool_calls=(ToolCall("call_1", "x", "{}"),))
|
|
assert turn.content == ()
|
|
assert turn.text == ""
|
|
|
|
|
|
def test_system_turn_source_marks_operator_context() -> None:
|
|
base = Turn.system("base prompt")
|
|
op = Turn.system("output-guard flagged this", source="output_guard")
|
|
assert base.source is None # base prompt
|
|
assert op.source == "output_guard" # operator-context turn
|
|
|
|
|
|
def test_turnmeta_defaults_are_independent() -> None:
|
|
# default_factory must not share a single dict across instances.
|
|
a = Turn.user("a")
|
|
b = Turn.user("b")
|
|
a.meta.extra["k"] = "v"
|
|
assert b.meta.extra == {}
|
|
assert isinstance(a.meta, TurnMeta)
|
|
|
|
|
|
def test_provider_native_defaults_to_empty_blocks() -> None:
|
|
assert ProviderNative(producer="google").blocks == ()
|