mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
Always apply conditional-line filtering so tagged prompt lines are removed unless the corresponding boolean flag is explicitly true. Add regressions for omitted flags with and without format variables. Test: pytest tests/unit/test_prompt_handler.py -q
300 lines
8.5 KiB
Python
300 lines
8.5 KiB
Python
"""Tests for PromptHandler."""
|
|
|
|
# pylint: disable=missing-function-docstring
|
|
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from reme.components.prompt_handler import PromptHandler
|
|
|
|
# -- init & load_prompt_dict --------------------------------------------------
|
|
|
|
|
|
def test_init_filters_non_string_values():
|
|
ph = PromptHandler(greeting="hello", count=42, items=[1, 2])
|
|
assert ph.data == {"greeting": "hello"}
|
|
|
|
|
|
def test_load_prompt_dict_basic():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"a": "alpha", "b": "beta"})
|
|
assert ph.data == {"a": "alpha", "b": "beta"}
|
|
|
|
|
|
def test_load_prompt_dict_skips_non_string_values():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"good": "ok", "bad": 123})
|
|
assert ph.data == {"good": "ok"}
|
|
|
|
|
|
def test_load_prompt_dict_overwrite_true():
|
|
ph = PromptHandler(a="old")
|
|
ph.load_prompt_dict({"a": "new"}, overwrite=True)
|
|
assert ph.data["a"] == "new"
|
|
|
|
|
|
def test_load_prompt_dict_overwrite_false():
|
|
ph = PromptHandler(a="old")
|
|
ph.load_prompt_dict({"a": "new"}, overwrite=False)
|
|
assert ph.data["a"] == "old"
|
|
|
|
|
|
def test_load_prompt_dict_none():
|
|
ph = PromptHandler(a="old")
|
|
result = ph.load_prompt_dict(None)
|
|
assert result is ph
|
|
assert ph.data == {"a": "old"}
|
|
|
|
|
|
def test_load_prompt_dict_non_dict():
|
|
ph = PromptHandler()
|
|
result = ph.load_prompt_dict("not a dict")
|
|
assert result is ph
|
|
assert ph.data == {}
|
|
|
|
|
|
# -- load from file -----------------------------------------------------------
|
|
|
|
|
|
def test_load_prompt_by_file_yaml():
|
|
data = {"greeting": "Hello {name}", "farewell": "Goodbye"}
|
|
with tempfile.NamedTemporaryFile(suffix=".yaml", mode="w", delete=False) as f:
|
|
yaml.dump(data, f)
|
|
f.flush()
|
|
ph = PromptHandler()
|
|
ph.load_prompt_by_file(f.name)
|
|
assert ph.data["greeting"] == "Hello {name}"
|
|
assert ph.data["farewell"] == "Goodbye"
|
|
Path(f.name).unlink()
|
|
|
|
|
|
def test_load_prompt_by_file_json():
|
|
data = {"q1": "What is {topic}?"}
|
|
with tempfile.NamedTemporaryFile(suffix=".json", mode="w", delete=False) as f:
|
|
json.dump(data, f)
|
|
f.flush()
|
|
ph = PromptHandler()
|
|
ph.load_prompt_by_file(f.name)
|
|
assert ph.data["q1"] == "What is {topic}?"
|
|
Path(f.name).unlink()
|
|
|
|
|
|
def test_load_prompt_by_file_none():
|
|
ph = PromptHandler()
|
|
result = ph.load_prompt_by_file(None)
|
|
assert result is ph
|
|
|
|
|
|
def test_load_prompt_by_file_nonexistent():
|
|
ph = PromptHandler()
|
|
result = ph.load_prompt_by_file("/nonexistent/path.yaml")
|
|
assert result is ph
|
|
assert ph.data == {}
|
|
|
|
|
|
def test_load_prompt_by_file_unsupported_extension():
|
|
with tempfile.NamedTemporaryFile(suffix=".txt", mode="w", delete=False) as f:
|
|
f.write("hello")
|
|
f.flush()
|
|
ph = PromptHandler()
|
|
result = ph.load_prompt_by_file(f.name)
|
|
assert result is ph
|
|
assert ph.data == {}
|
|
Path(f.name).unlink()
|
|
|
|
|
|
# -- get_prompt & i18n --------------------------------------------------------
|
|
|
|
|
|
def test_get_prompt_bare_key():
|
|
ph = PromptHandler(greeting="Hello")
|
|
assert ph.get_prompt("greeting") == "Hello"
|
|
|
|
|
|
def test_get_prompt_strips():
|
|
ph = PromptHandler(greeting=" Hello \n")
|
|
assert ph.get_prompt("greeting") == "Hello"
|
|
|
|
|
|
def test_get_prompt_missing_raises():
|
|
ph = PromptHandler()
|
|
with pytest.raises(KeyError, match="not found"):
|
|
ph.get_prompt("missing")
|
|
|
|
|
|
def test_get_prompt_language_fallback():
|
|
ph = PromptHandler(language="zh", greeting="Hello", greeting_zh="你好")
|
|
assert ph.get_prompt("greeting") == "你好"
|
|
|
|
|
|
def test_get_prompt_language_fallback_to_bare():
|
|
ph = PromptHandler(language="zh", greeting="Hello")
|
|
assert ph.get_prompt("greeting") == "Hello"
|
|
|
|
|
|
def test_has_prompt():
|
|
ph = PromptHandler(greeting="Hello")
|
|
assert ph.has_prompt("greeting") is True
|
|
assert ph.has_prompt("missing") is False
|
|
|
|
|
|
def test_has_prompt_with_language():
|
|
ph = PromptHandler(language="en", greeting_en="Hi")
|
|
assert ph.has_prompt("greeting") is True
|
|
|
|
|
|
# -- list_prompts -------------------------------------------------------------
|
|
|
|
|
|
def test_list_prompts_all():
|
|
ph = PromptHandler(a="1", b_en="2", c_zh="3")
|
|
assert sorted(ph.list_prompts()) == ["a", "b_en", "c_zh"]
|
|
|
|
|
|
def test_list_prompts_filtered():
|
|
ph = PromptHandler(a="1", b_en="2", c_en="3", d_zh="4")
|
|
assert sorted(ph.list_prompts("en")) == ["b_en", "c_en"]
|
|
|
|
|
|
# -- prompt_format (flag filtering) -------------------------------------------
|
|
|
|
|
|
def test_flag_filter_keeps_matching():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "[verbose] extra detail\nalways here"})
|
|
result = ph.prompt_format("p", verbose=True)
|
|
assert "extra detail" in result
|
|
assert "always here" in result
|
|
|
|
|
|
def test_flag_filter_removes_non_matching():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "[verbose] extra detail\nalways here"})
|
|
result = ph.prompt_format("p", verbose=False)
|
|
assert "extra detail" not in result
|
|
assert "always here" in result
|
|
|
|
|
|
def test_flag_filter_default_false():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "[debug] debug info\nbase"})
|
|
result = ph.prompt_format("p")
|
|
assert "debug info" not in result
|
|
assert "base" in result
|
|
|
|
|
|
def test_flag_filter_defaults_false_with_format_variables():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "[debug] debug {name}\nhello {name}"})
|
|
result = ph.prompt_format("p", name="Alice")
|
|
assert result == "hello Alice"
|
|
|
|
|
|
def test_flag_filter_unflagged_lines_always_kept():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "line1\nline2\nline3"})
|
|
result = ph.prompt_format("p")
|
|
assert "line1" in result
|
|
assert "line2" in result
|
|
assert "line3" in result
|
|
|
|
|
|
# -- prompt_format (variable substitution) ------------------------------------
|
|
|
|
|
|
def test_format_variables():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "Hello {name}, welcome to {place}"})
|
|
result = ph.prompt_format("p", name="Alice", place="Wonderland")
|
|
assert result == "Hello Alice, welcome to Wonderland"
|
|
|
|
|
|
def test_format_missing_variable_raises():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "Hello {name}, welcome to {place}"})
|
|
with pytest.raises(KeyError, match="place"):
|
|
ph.prompt_format("p", name="Alice")
|
|
|
|
|
|
def test_format_no_variables_no_error():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict({"p": "No vars here"})
|
|
result = ph.prompt_format("p")
|
|
assert result == "No vars here"
|
|
|
|
|
|
# -- prompt_format (combined flags + variables) -------------------------------
|
|
|
|
|
|
def test_format_flags_and_variables_combined():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict(
|
|
{
|
|
"p": "[verbose] Debug: {detail}\nResult: {answer}",
|
|
},
|
|
)
|
|
result = ph.prompt_format("p", verbose=True, detail="trace", answer="42")
|
|
assert "Debug: trace" in result
|
|
assert "Result: 42" in result
|
|
|
|
|
|
def test_format_flags_false_variable_not_needed():
|
|
ph = PromptHandler()
|
|
ph.load_prompt_dict(
|
|
{
|
|
"p": "[verbose] Debug: {detail}\nResult: {answer}",
|
|
},
|
|
)
|
|
result = ph.prompt_format("p", verbose=False, answer="42")
|
|
assert "Debug" not in result
|
|
assert "Result: 42" in result
|
|
|
|
|
|
# -- repr ---------------------------------------------------------------------
|
|
|
|
|
|
def test_repr():
|
|
ph = PromptHandler(language="en", a="1", b="2")
|
|
r = repr(ph)
|
|
assert "en" in r
|
|
assert "2" in r
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("\n=== PromptHandler Tests ===")
|
|
test_init_filters_non_string_values()
|
|
test_load_prompt_dict_basic()
|
|
test_load_prompt_dict_skips_non_string_values()
|
|
test_load_prompt_dict_overwrite_true()
|
|
test_load_prompt_dict_overwrite_false()
|
|
test_load_prompt_dict_none()
|
|
test_load_prompt_dict_non_dict()
|
|
test_load_prompt_by_file_yaml()
|
|
test_load_prompt_by_file_json()
|
|
test_load_prompt_by_file_none()
|
|
test_load_prompt_by_file_nonexistent()
|
|
test_load_prompt_by_file_unsupported_extension()
|
|
test_get_prompt_bare_key()
|
|
test_get_prompt_strips()
|
|
test_get_prompt_missing_raises()
|
|
test_get_prompt_language_fallback()
|
|
test_get_prompt_language_fallback_to_bare()
|
|
test_has_prompt()
|
|
test_has_prompt_with_language()
|
|
test_list_prompts_all()
|
|
test_list_prompts_filtered()
|
|
test_flag_filter_keeps_matching()
|
|
test_flag_filter_removes_non_matching()
|
|
test_flag_filter_default_false()
|
|
test_flag_filter_unflagged_lines_always_kept()
|
|
test_format_variables()
|
|
test_format_missing_variable_raises()
|
|
test_format_no_variables_no_error()
|
|
test_format_flags_and_variables_combined()
|
|
test_format_flags_false_variable_not_needed()
|
|
test_repr()
|
|
print("\n所有测试通过!")
|