fix: support conversational mode draft updates
This commit is contained in:
@@ -341,6 +341,7 @@ def _stream(
|
||||
execute_control: Callable[..., dict[str, object]] | None = None,
|
||||
capability_snapshot: dict[str, object] | None = None,
|
||||
capability_context: dict[str, object] | None = None,
|
||||
mode_profile_draft_context: dict[str, object] | None = None,
|
||||
) -> tuple[dict[str, object], list[dict[str, object]]]:
|
||||
events: list[dict[str, object]] = []
|
||||
result = assistant.stream(
|
||||
@@ -354,6 +355,7 @@ def _stream(
|
||||
control_terms=CONTROL_TERMS,
|
||||
capability_snapshot=capability_snapshot,
|
||||
capability_context=capability_context,
|
||||
mode_profile_draft_context=mode_profile_draft_context,
|
||||
)
|
||||
return result, events
|
||||
|
||||
@@ -426,6 +428,181 @@ def test_registered_name_only_mode_keeps_the_fast_local_route_without_codex() ->
|
||||
assert events[0]["engine"] == "n7web-mode-builder"
|
||||
|
||||
|
||||
def test_pending_mode_draft_update_uses_fast_configuration_route_without_codex() -> None:
|
||||
client = FakeCodexClient(available=False)
|
||||
assistant = CodexAssistantService(client)
|
||||
calls: list[str] = []
|
||||
result_payload = _sample_mode_preview_result(
|
||||
"小憩模式",
|
||||
generation_source="draft_revision",
|
||||
existing_mode_id="nap",
|
||||
)
|
||||
draft = result_payload["mode_profile_draft"]
|
||||
assert isinstance(draft, dict)
|
||||
draft.update(
|
||||
{
|
||||
"source_request_id": "req-mode-revision",
|
||||
"draft_revision": 2,
|
||||
"revises_source_request_id": "req-mode-preview",
|
||||
}
|
||||
)
|
||||
|
||||
result, events = _stream(
|
||||
assistant,
|
||||
"把小憩模式的空调温度调整到25度",
|
||||
execute_control=lambda instruction, _progress: (
|
||||
calls.append(instruction) or result_payload
|
||||
),
|
||||
mode_profile_draft_context={
|
||||
"name": "小憩模式",
|
||||
"source_request_id": "req-mode-preview",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["route"] == "vehicle_control"
|
||||
assert result["success"] is True
|
||||
assert calls == ["把小憩模式的空调温度调整到25度"]
|
||||
assert client.turn_calls == []
|
||||
assert events[0]["engine"] == "n7web-mode-builder"
|
||||
assert "调整待保存" in result["answer"]
|
||||
|
||||
|
||||
def test_active_draft_context_does_not_hijack_an_ordinary_question() -> None:
|
||||
client = FakeCodexClient()
|
||||
assistant = CodexAssistantService(client)
|
||||
|
||||
result, events = _stream(
|
||||
assistant,
|
||||
"解释一下空调内循环和外循环的区别",
|
||||
mode_profile_draft_context={
|
||||
"name": "小憩模式",
|
||||
"source_request_id": "req-mode-preview",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
)
|
||||
|
||||
assert result == {"route": "agent", "answer": "这是 Codex 的流式回答。"}
|
||||
assert client.turn_calls[0]["text"] == "解释一下空调内循环和外循环的区别"
|
||||
assert events[0]["engine"] == "codex-app-server"
|
||||
|
||||
|
||||
def test_malformed_stale_draft_context_is_ignored_for_an_ordinary_question() -> None:
|
||||
client = FakeCodexClient()
|
||||
assistant = CodexAssistantService(client)
|
||||
events: list[dict[str, object]] = []
|
||||
|
||||
result = assistant.stream(
|
||||
"解释一下空调内循环和外循环的区别",
|
||||
conversation_id="conv-stale-draft-context",
|
||||
principal="alice",
|
||||
history=[],
|
||||
emit=lambda event: events.append(event),
|
||||
execute_control=lambda _instruction, _progress: pytest.fail(
|
||||
"vehicle control was unexpected"
|
||||
),
|
||||
control_terms=CONTROL_TERMS,
|
||||
mode_profile_draft_context={"untrusted": "stale"},
|
||||
)
|
||||
|
||||
assert result["route"] == "agent"
|
||||
assert events[0]["engine"] == "codex-app-server"
|
||||
|
||||
|
||||
def test_malformed_matching_draft_reference_is_rejected_before_control() -> None:
|
||||
assistant = CodexAssistantService(FakeCodexClient(available=False))
|
||||
|
||||
with pytest.raises(ValueError, match="fields are invalid"):
|
||||
assistant.stream(
|
||||
"把小憩模式的空调温度调整到25度",
|
||||
conversation_id="conv-invalid-draft-context",
|
||||
principal="alice",
|
||||
history=[],
|
||||
emit=lambda _event: True,
|
||||
execute_control=lambda _instruction, _progress: pytest.fail(
|
||||
"vehicle control was unexpected"
|
||||
),
|
||||
control_terms=CONTROL_TERMS,
|
||||
mode_profile_draft_context={"name": "小憩模式"},
|
||||
)
|
||||
|
||||
|
||||
def test_nonmatching_draft_context_does_not_claim_another_mode_update() -> None:
|
||||
authorization = _authorize_vehicle_request(
|
||||
"把观影模式的空调温度调整到25度",
|
||||
CONTROL_TERMS,
|
||||
{
|
||||
"name": "小憩模式",
|
||||
"source_request_id": "req-mode-preview",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
)
|
||||
|
||||
# The stale card reference has been ignored. The sentence is still an
|
||||
# explicit registered control under the legacy saved-mode update route.
|
||||
assert authorization.reason == "explicit_registered_control"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"message",
|
||||
(
|
||||
"把小憩模式的空调介绍一下",
|
||||
"小憩模式的空调有什么作用",
|
||||
"小憩模式的空调和普通空调有什么区别",
|
||||
"小憩模式的空调设置有哪些?",
|
||||
"小憩模式的空调该调几度",
|
||||
"把小憩模式的空调调到25度合适吗",
|
||||
"调整小憩模式的空调参数,列出来",
|
||||
),
|
||||
)
|
||||
def test_device_word_air_conditioner_does_not_fake_a_draft_update_verb(
|
||||
message: str,
|
||||
) -> None:
|
||||
authorization = _authorize_vehicle_request(
|
||||
message,
|
||||
CONTROL_TERMS,
|
||||
{
|
||||
"name": "小憩模式",
|
||||
"source_request_id": "req-mode-preview",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
)
|
||||
|
||||
assert authorization.reason != "explicit_mode_profile_draft_update"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"message",
|
||||
(
|
||||
"修改小憩模式的空调设置是什么",
|
||||
"小憩模式的空调该调几度",
|
||||
"把小憩模式的空调调到25度合适吗",
|
||||
"调整小憩模式的空调参数,列出来",
|
||||
"小憩模式的空调调到25度会怎样",
|
||||
),
|
||||
)
|
||||
def test_mode_profile_question_with_control_words_never_calls_vehicle_executor(
|
||||
message: str,
|
||||
) -> None:
|
||||
assistant = CodexAssistantService(FakeCodexClient())
|
||||
|
||||
result, events = _stream(
|
||||
assistant,
|
||||
message,
|
||||
execute_control=lambda _instruction, _progress: pytest.fail(
|
||||
"mode-profile question reached vehicle executor"
|
||||
),
|
||||
mode_profile_draft_context={
|
||||
"name": "小憩模式",
|
||||
"source_request_id": "req-mode-preview",
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
)
|
||||
|
||||
assert result["route"] == "agent"
|
||||
assert events[0]["engine"] == "codex-app-server"
|
||||
|
||||
|
||||
def test_unknown_name_only_mode_uses_codex_and_overrides_model_execution_claim() -> None:
|
||||
client = FakeCodexClient()
|
||||
assistant = CodexAssistantService(client)
|
||||
|
||||
Reference in New Issue
Block a user