331 lines
10 KiB
Python
331 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from core.turn_types import GenerationRunResult
|
|
from services.generation_execution_service import GenerationExecutionService
|
|
from services.turn_execution_service import (
|
|
SessionChatNodeExecutionDependencies,
|
|
SessionChatNodeExecutionRequest,
|
|
SessionChatNodeExecutionService,
|
|
TurnExecutionRequest,
|
|
TurnExecutionResult,
|
|
TurnExecutionService,
|
|
)
|
|
from turn_execution_helpers import (
|
|
DummyManager,
|
|
DummyLogRequest,
|
|
_base_deps,
|
|
_capture_generation_kwargs,
|
|
_make_request,
|
|
)
|
|
import pytest
|
|
|
|
|
|
def test_execute_from_node_inputs_matches_execute_turn_path() -> None:
|
|
service = TurnExecutionService()
|
|
mgr = DummyManager()
|
|
history = {"turns": [], "summary": {"enabled": False, "text": ""}, "meta": {}}
|
|
deps, writes = _base_deps(
|
|
history,
|
|
run_generation_result=GenerationRunResult(
|
|
assistant_text="assistant reply",
|
|
gen_tokens=64,
|
|
turns_limit=12,
|
|
last_err=None,
|
|
succeeded=True,
|
|
non_ctx_error=False,
|
|
),
|
|
)
|
|
|
|
result = service.execute_from_node_inputs(
|
|
user_text="hello",
|
|
session_id="sid",
|
|
model="model.gguf",
|
|
mmproj="(Auto detect)",
|
|
system_prompt="sys",
|
|
max_tokens=64,
|
|
temperature=0.7,
|
|
top_p=0.9,
|
|
n_gpu_layers=0,
|
|
n_ctx=1024,
|
|
media=None,
|
|
max_turns=12,
|
|
summarize_old_history=True,
|
|
summary_chunk_turns=3,
|
|
max_tokens_summary=128,
|
|
summary_max_chars=1500,
|
|
dynamic_max_tokens=True,
|
|
min_generation_tokens=96,
|
|
safety_margin_tokens=64,
|
|
persistent_cache="off",
|
|
repeat_penalty=1.12,
|
|
repeat_last_n=256,
|
|
rewrite_continue=True,
|
|
runtime_cache="off",
|
|
log_level="timing",
|
|
suppress_backend_logs=True,
|
|
history_dir="",
|
|
reset_session=False,
|
|
stream_to_console=False,
|
|
model_manager=mgr,
|
|
chat_handler_overrides=None,
|
|
text_chat_builder_overrides=None,
|
|
strip_assistant_before_reasoning_filter=False,
|
|
include_media_and_stream_in_turn_params=True,
|
|
kv_log_saved_when_not_minimal=False,
|
|
kv_log_unsupported_when_not_minimal=False,
|
|
include_error_in_invalidate_message=False,
|
|
enable_attempt_logging=False,
|
|
log_prefix="[LLM Session Chat]",
|
|
dependencies=deps,
|
|
)
|
|
|
|
assert result.generation_succeeded is True
|
|
assert result.assistant_text == "assistant reply"
|
|
assert writes and writes[0][0] == "hist.json"
|
|
|
|
|
|
def test_execute_session_chat_turn_adds_media_stream_params() -> None:
|
|
service = TurnExecutionService()
|
|
mgr = DummyManager()
|
|
history = {"turns": [], "summary": {"enabled": False, "text": ""}, "meta": {}}
|
|
deps, _writes = _base_deps(
|
|
history,
|
|
run_generation_result=GenerationRunResult(
|
|
assistant_text="assistant reply",
|
|
gen_tokens=64,
|
|
turns_limit=12,
|
|
last_err=None,
|
|
succeeded=True,
|
|
non_ctx_error=False,
|
|
),
|
|
)
|
|
|
|
result = service.execute_session_chat_turn(
|
|
user_text="hello",
|
|
session_id="sid",
|
|
model="model.gguf",
|
|
mmproj="(Auto detect)",
|
|
system_prompt="sys",
|
|
max_tokens=64,
|
|
temperature=0.7,
|
|
top_p=0.9,
|
|
n_gpu_layers=0,
|
|
n_ctx=1024,
|
|
media=None,
|
|
max_turns=12,
|
|
summarize_old_history=True,
|
|
summary_chunk_turns=3,
|
|
max_tokens_summary=128,
|
|
summary_max_chars=1500,
|
|
dynamic_max_tokens=True,
|
|
min_generation_tokens=96,
|
|
safety_margin_tokens=64,
|
|
persistent_cache="off",
|
|
repeat_penalty=1.12,
|
|
repeat_last_n=256,
|
|
rewrite_continue=True,
|
|
runtime_cache="off",
|
|
log_level="timing",
|
|
suppress_backend_logs=True,
|
|
history_dir="",
|
|
reset_session=False,
|
|
stream_to_console=False,
|
|
model_manager=mgr,
|
|
chat_handler_overrides=None,
|
|
text_chat_builder_overrides=None,
|
|
dependencies=deps,
|
|
)
|
|
|
|
assert result.generation_succeeded is True
|
|
params = history["turns"][0]["params"]
|
|
assert params["image_used"] is False
|
|
assert params["image_count"] == 0
|
|
assert params["audio_used"] is False
|
|
assert params["audio_format"] == ""
|
|
assert "streamed" in params
|
|
user = history["turns"][0]["user"]
|
|
assert "image_note" in user
|
|
assert "audio_note" in user
|
|
assert "media_note" not in user
|
|
|
|
|
|
def test_execute_session_chat_turn_records_image_media_params() -> None:
|
|
service = TurnExecutionService()
|
|
mgr = DummyManager()
|
|
history = {"turns": [], "summary": {"enabled": False, "text": ""}, "meta": {}}
|
|
deps, _writes = _base_deps(
|
|
history,
|
|
run_generation_result=GenerationRunResult(
|
|
assistant_text="assistant reply",
|
|
gen_tokens=64,
|
|
turns_limit=12,
|
|
last_err=None,
|
|
succeeded=True,
|
|
non_ctx_error=False,
|
|
),
|
|
)
|
|
|
|
request = _make_request(deps, mgr)
|
|
media = type("ImageMedia", (), {"shape": (3, 16, 16, 3)})()
|
|
request = TurnExecutionRequest(**{**request.__dict__, "media": media})
|
|
|
|
result = service.execute_turn(request)
|
|
|
|
assert result.generation_succeeded is True
|
|
params = history["turns"][0]["params"]
|
|
assert params["image_used"] is True
|
|
assert params["image_count"] == 3
|
|
assert params["audio_used"] is False
|
|
assert params["audio_format"] == ""
|
|
|
|
|
|
def test_execute_session_chat_turn_records_audio_media_params() -> None:
|
|
service = TurnExecutionService()
|
|
mgr = DummyManager()
|
|
history = {"turns": [], "summary": {"enabled": False, "text": ""}, "meta": {}}
|
|
deps, _writes = _base_deps(
|
|
history,
|
|
run_generation_result=GenerationRunResult(
|
|
assistant_text="assistant reply",
|
|
gen_tokens=64,
|
|
turns_limit=12,
|
|
last_err=None,
|
|
succeeded=True,
|
|
non_ctx_error=False,
|
|
),
|
|
)
|
|
|
|
request = _make_request(deps, mgr)
|
|
request = TurnExecutionRequest(**{**request.__dict__, "media": {"waveform": object(), "sample_rate": 16000}})
|
|
|
|
result = service.execute_turn(request)
|
|
|
|
assert result.generation_succeeded is True
|
|
params = history["turns"][0]["params"]
|
|
assert params["image_used"] is False
|
|
assert params["image_count"] == 0
|
|
assert params["audio_used"] is True
|
|
assert params["audio_format"] == "wav"
|
|
|
|
|
|
def test_execute_dialogue_cycle_turn_strips_and_omits_media_stream_params() -> None:
|
|
service = TurnExecutionService()
|
|
mgr = DummyManager()
|
|
history = {"turns": [], "summary": {"enabled": False, "text": ""}, "meta": {}}
|
|
deps, _writes = _base_deps(
|
|
history,
|
|
run_generation_result=GenerationRunResult(
|
|
assistant_text=" assistant reply ",
|
|
gen_tokens=64,
|
|
turns_limit=12,
|
|
last_err=None,
|
|
succeeded=True,
|
|
non_ctx_error=False,
|
|
),
|
|
)
|
|
|
|
result = service.execute_dialogue_cycle_turn(
|
|
user_text="hello",
|
|
session_id="sid",
|
|
model="model.gguf",
|
|
mmproj="(Auto detect)",
|
|
system_prompt="sys",
|
|
max_tokens=64,
|
|
temperature=0.7,
|
|
top_p=0.9,
|
|
n_gpu_layers=0,
|
|
n_ctx=1024,
|
|
media=None,
|
|
max_turns=12,
|
|
summarize_old_history=True,
|
|
summary_chunk_turns=3,
|
|
max_tokens_summary=128,
|
|
summary_max_chars=1500,
|
|
dynamic_max_tokens=True,
|
|
min_generation_tokens=96,
|
|
safety_margin_tokens=64,
|
|
persistent_cache="off",
|
|
repeat_penalty=1.12,
|
|
repeat_last_n=256,
|
|
rewrite_continue=True,
|
|
runtime_cache="off",
|
|
log_level="timing",
|
|
suppress_backend_logs=True,
|
|
history_dir="",
|
|
reset_session=False,
|
|
stream_to_console=False,
|
|
model_manager=mgr,
|
|
chat_handler_overrides=None,
|
|
text_chat_builder_overrides=None,
|
|
dependencies=deps,
|
|
)
|
|
|
|
assert result.generation_succeeded is True
|
|
assert result.assistant_text == "assistant reply"
|
|
params = history["turns"][0]["params"]
|
|
assert "image_used" not in params
|
|
assert "audio_used" not in params
|
|
assert "streamed" not in params
|
|
|
|
|
|
def test_execute_session_chat_turn_sets_profile_flags() -> None:
|
|
service = TurnExecutionService()
|
|
captured: dict[str, object] = {}
|
|
|
|
def _spy_execute_from_node_inputs(**kwargs):
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
service.execute_from_node_inputs = _spy_execute_from_node_inputs # type: ignore[method-assign]
|
|
result = service.execute_session_chat_turn(user_text="hello")
|
|
|
|
assert result == "ok"
|
|
assert captured["strip_assistant_before_reasoning_filter"] is False
|
|
assert captured["include_media_and_stream_in_turn_params"] is True
|
|
assert captured["kv_log_saved_when_not_minimal"] is False
|
|
assert captured["kv_log_unsupported_when_not_minimal"] is False
|
|
assert captured["include_error_in_invalidate_message"] is False
|
|
assert captured["enable_attempt_logging"] is True
|
|
assert captured["log_prefix"] == "[LLM Session Chat]"
|
|
|
|
|
|
def test_execute_dialogue_cycle_turn_sets_profile_flags() -> None:
|
|
service = TurnExecutionService()
|
|
captured: dict[str, object] = {}
|
|
|
|
def _spy_execute_from_node_inputs(**kwargs):
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
service.execute_from_node_inputs = _spy_execute_from_node_inputs # type: ignore[method-assign]
|
|
result = service.execute_dialogue_cycle_turn(user_text="hello")
|
|
|
|
assert result == "ok"
|
|
assert captured["strip_assistant_before_reasoning_filter"] is True
|
|
assert captured["include_media_and_stream_in_turn_params"] is False
|
|
assert captured["kv_log_saved_when_not_minimal"] is True
|
|
assert captured["kv_log_unsupported_when_not_minimal"] is True
|
|
assert captured["include_error_in_invalidate_message"] is True
|
|
assert captured["enable_attempt_logging"] is True
|
|
assert captured["log_prefix"] == "[LLM Dialogue Cycle]"
|
|
|
|
|
|
def test_execute_dialogue_cycle_turn_accepts_log_prefix_override() -> None:
|
|
service = TurnExecutionService()
|
|
captured: dict[str, object] = {}
|
|
|
|
def _spy_execute_from_node_inputs(**kwargs):
|
|
captured.update(kwargs)
|
|
return "ok"
|
|
|
|
service.execute_from_node_inputs = _spy_execute_from_node_inputs # type: ignore[method-assign]
|
|
result = service.execute_dialogue_cycle_turn(
|
|
user_text="hello",
|
|
log_prefix_override="[LLM Dialogue Cycle A/1]",
|
|
)
|
|
|
|
assert result == "ok"
|
|
assert captured["enable_attempt_logging"] is True
|
|
assert captured["log_prefix"] == "[LLM Dialogue Cycle A/1]"
|
|
assert "log_prefix_override" not in captured
|