Merge 4112f949b4 into 2ad56a4d71
This commit is contained in:
commit
8aee461e45
|
|
@ -80,6 +80,10 @@ EXAMPLES (using `alice` as the target peer id):
|
||||||
- EXPLICIT: "I've lived in NYC for six years" → "alice lives in NYC", "alice has lived in NYC for six years"
|
- EXPLICIT: "I've lived in NYC for six years" → "alice lives in NYC", "alice has lived in NYC for six years"
|
||||||
</examples>
|
</examples>
|
||||||
|
|
||||||
|
Respond with a json object in the following format:
|
||||||
|
{{"explicit": [{{"content": "fact 1"}}, {{"content": "fact 2"}}]}}
|
||||||
|
Each item in "explicit" must be an object with a "content" field, not a bare string.
|
||||||
|
|
||||||
{custom_instructions_section}
|
{custom_instructions_section}
|
||||||
|
|
||||||
Target peer:
|
Target peer:
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,12 @@ class PromptRepresentation(BaseModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
explicit: list[ExplicitObservationBase] = Field(
|
explicit: list[ExplicitObservationBase] = Field(
|
||||||
description="Facts LITERALLY stated by the user - direct quotes or clear paraphrases only, no interpretation or inference. Example: ['The user is 25 years old', 'The user has a dog named Rover']",
|
description=(
|
||||||
|
"Facts LITERALLY stated by the user - direct quotes or clear "
|
||||||
|
"paraphrases only, no interpretation or inference. "
|
||||||
|
'Example: [{"content": "The user is 25 years old"}, '
|
||||||
|
'{"content": "The user has a dog named Rover"}]'
|
||||||
|
),
|
||||||
default_factory=list,
|
default_factory=list,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,27 @@
|
||||||
|
from collections.abc import AsyncGenerator, Generator
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from src.deriver.prompts import (
|
from src.deriver.prompts import (
|
||||||
estimate_deriver_prompt_tokens,
|
estimate_deriver_prompt_tokens,
|
||||||
estimate_minimal_deriver_prompt_tokens,
|
estimate_minimal_deriver_prompt_tokens,
|
||||||
minimal_deriver_prompt,
|
minimal_deriver_prompt,
|
||||||
)
|
)
|
||||||
|
from src.utils.representation import PromptRepresentation
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
async def clean_queue_tables() -> AsyncGenerator[None, None]:
|
||||||
|
"""Prompt-only tests do not need queue table cleanup."""
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def mock_tracked_db() -> Generator[None, None, None]:
|
||||||
|
"""Prompt-only tests do not need tracked_db patching."""
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
def test_minimal_deriver_prompt_includes_custom_instructions_when_present() -> None:
|
def test_minimal_deriver_prompt_includes_custom_instructions_when_present() -> None:
|
||||||
|
|
@ -30,6 +45,30 @@ def test_minimal_deriver_prompt_omits_custom_instructions_when_absent() -> None:
|
||||||
assert "CUSTOM INSTRUCTIONS:" not in prompt
|
assert "CUSTOM INSTRUCTIONS:" not in prompt
|
||||||
|
|
||||||
|
|
||||||
|
def test_minimal_deriver_prompt_describes_explicit_json_object_shape() -> None:
|
||||||
|
prompt = minimal_deriver_prompt(
|
||||||
|
peer_id="alice",
|
||||||
|
messages="alice: I just had my 25th birthday",
|
||||||
|
custom_instructions=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert '"explicit": [{"content": "fact 1"}' in prompt
|
||||||
|
assert 'Each item in "explicit" must be an object with a "content" field' in prompt
|
||||||
|
assert "not a bare string" in prompt
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
PromptRepresentation.model_validate_json(
|
||||||
|
'{"explicit": ["alice is 25 years old"]}'
|
||||||
|
)
|
||||||
|
assert PromptRepresentation.model_validate_json(
|
||||||
|
'{"explicit": [{"content": "alice is 25 years old"}]}'
|
||||||
|
).explicit[0].content == "alice is 25 years old"
|
||||||
|
explicit_schema_description = PromptRepresentation.model_json_schema()["properties"][
|
||||||
|
"explicit"
|
||||||
|
]["description"]
|
||||||
|
assert '{"content": "The user is 25 years old"}' in explicit_schema_description
|
||||||
|
assert "['The user is 25 years old'" not in explicit_schema_description
|
||||||
|
|
||||||
|
|
||||||
def test_estimate_deriver_prompt_tokens_increases_with_custom_instructions() -> None:
|
def test_estimate_deriver_prompt_tokens_increases_with_custom_instructions() -> None:
|
||||||
base_tokens = estimate_minimal_deriver_prompt_tokens()
|
base_tokens = estimate_minimal_deriver_prompt_tokens()
|
||||||
custom_tokens = estimate_deriver_prompt_tokens(
|
custom_tokens = estimate_deriver_prompt_tokens(
|
||||||
|
|
|
||||||
|
|
@ -893,6 +893,38 @@ async def test_structured_output_json_object_mode_request_shape() -> None:
|
||||||
assert result.content.answer == "ok"
|
assert result.content.answer == "ok"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_structured_output_json_object_mode_prompt_representation_schema_shape() -> None:
|
||||||
|
"""PromptRepresentation schema hints explicit observations as content objects."""
|
||||||
|
client = Mock()
|
||||||
|
client.chat.completions.parse = AsyncMock()
|
||||||
|
client.chat.completions.create = AsyncMock(
|
||||||
|
return_value=_structured_create_return(
|
||||||
|
'{"explicit": [{"content": "alice likes coffee"}]}'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
backend = OpenAIBackend(client)
|
||||||
|
result = await backend.complete(
|
||||||
|
model="glm-4.6",
|
||||||
|
messages=[{"role": "user", "content": "Hello"}],
|
||||||
|
max_tokens=100,
|
||||||
|
response_format=PromptRepresentation,
|
||||||
|
extra_params={"structured_output_mode": "json_object"},
|
||||||
|
)
|
||||||
|
|
||||||
|
call = _await_kwargs(client.chat.completions.create)
|
||||||
|
system_messages = [m for m in call["messages"] if m["role"] == "system"]
|
||||||
|
assert system_messages, "expected a system message carrying the schema"
|
||||||
|
system_content = system_messages[0]["content"]
|
||||||
|
schema = json.loads(system_content.split("JSON schema:\n", 1)[1])
|
||||||
|
explicit_description = schema["properties"]["explicit"]["description"]
|
||||||
|
assert '{"content": "The user is 25 years old"}' in explicit_description
|
||||||
|
assert "['The user is 25 years old'" not in explicit_description
|
||||||
|
assert isinstance(result.content, PromptRepresentation)
|
||||||
|
assert result.content.explicit[0].content == "alice likes coffee"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_structured_output_json_object_mode_repairs_markdown() -> None:
|
async def test_structured_output_json_object_mode_repairs_markdown() -> None:
|
||||||
"""A provider that ignores json_object and returns prose must not crash —
|
"""A provider that ignores json_object and returns prose must not crash —
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue