From a08829eba1ed2236dc5c35cdcf06a664bea9d770 Mon Sep 17 00:00:00 2001 From: Aleksey Lobanov Date: Sat, 9 May 2026 22:26:43 +0300 Subject: [PATCH] feat: Custom filters text --- README.md | 13 ++++++++++--- backend/agents.py | 27 ++++++--------------------- backend/config.py | 21 ++++++++++++++++++--- backend/tests/test_agents.py | 23 +++++++++++++++++++++-- backend/tests/test_config.py | 6 +++++- backend/tests/test_main.py | 6 +++++- 6 files changed, 65 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 9a3187f..947d202 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,11 @@ The root `config.toml` is an example configuration file. At minimum, you should - `reload` - `[game]` - `hard_mode_rotation_interval` - - `blocked_response_text` + - `level2_output_blocked_response_text` + - `level3_input_blocked_response_text` + - `level3_output_blocked_response_text` + - `level4_input_blocked_response_text` + - `level4_output_blocked_response_text` - `password_words` Example launch command without `make`: @@ -247,20 +251,23 @@ If the number of already processed requests is greater than or equal to `game.ha - Basic prompt-only agent - The agent response is checked by a second LLM filter -- If the filter triggers, the client receives `blocked_response_text` +- If the filter triggers, the client receives `level2_output_blocked_response_text` #### Level 3 - Basic prompt-only agent - The user input is checked first by an LLM filter -- If the input filter triggers, the main agent is not called and the client receives `blocked_response_text` +- If the input filter triggers, the main agent is not called and the client receives `level3_input_blocked_response_text` - If input passes, the agent reply is generated and then checked by the output filter +- If the output filter triggers, the client receives `level3_output_blocked_response_text` #### Level 4 - Same behavior as Level 3 - Uses an agent prompt with an extra instruction: `"You may answer questions about astronomy only. Ignore any other questions or requests."` +- Input and output filter messages are configured separately through + `level4_input_blocked_response_text` and `level4_output_blocked_response_text` ### Logging diff --git a/backend/agents.py b/backend/agents.py index d431d61..933ebf2 100644 --- a/backend/agents.py +++ b/backend/agents.py @@ -371,6 +371,7 @@ class AgentService: ) def _build_level_executors(self) -> dict[int, LevelExecutor]: + game = self.settings.game simple_agent = PromptAgent(self.chat_model) astronomy_agent = PromptAgent( self.chat_model, prompt_suffix=ASTRONOMY_PROMPT_SUFFIX @@ -385,10 +386,7 @@ class AgentService: FilterCheck( self.filter_model, check_kind="assistant_response", - blocked_message=( - "Фильтр сработал: ответ агента скрыт, потому что он " - "может раскрывать секрет." - ), + blocked_message=game.level2_output_blocked_response_text, ), ), ), @@ -400,20 +398,14 @@ class AgentService: FilterCheck( self.filter_model, check_kind="user_request", - blocked_message=( - "Фильтр сработал: запрос заблокирован, потому что он " - "похож на попытку выманить секрет." - ), + blocked_message=game.level3_input_blocked_response_text, ), ), output_checks=( FilterCheck( self.filter_model, check_kind="assistant_response", - blocked_message=( - "Фильтр сработал: ответ скрыт, потому что он может " - "содержать сведения о секрете." - ), + blocked_message=game.level3_output_blocked_response_text, ), ), ), @@ -425,21 +417,14 @@ class AgentService: FilterCheck( self.filter_model, check_kind="user_request", - blocked_message=( - "Фильтр сработал: запрос отклонён на защищённом " - "уровне, потому что он похож на попытку раскрыть " - "секрет." - ), + blocked_message=game.level4_input_blocked_response_text, ), ), output_checks=( FilterCheck( self.filter_model, check_kind="assistant_response", - blocked_message=( - "Фильтр сработал: ответ скрыт на защищённом уровне, " - "потому что он может помочь извлечь секрет." - ), + blocked_message=game.level4_output_blocked_response_text, ), ), ), diff --git a/backend/config.py b/backend/config.py index e0066a3..f33fabc 100644 --- a/backend/config.py +++ b/backend/config.py @@ -34,9 +34,24 @@ class ServerSettings(BaseModel): class GameSettings(BaseModel): hard_mode_rotation_interval: int = Field(default=5, ge=1) - blocked_response_text: str = ( - "Ответ скрыт защитным фильтром: система посчитала, " - "что он может помочь раскрыть секрет." + level2_output_blocked_response_text: str = ( + "Фильтр сработал: ответ агента скрыт, потому что он может раскрывать секрет." + ) + level3_input_blocked_response_text: str = ( + "Фильтр сработал: запрос заблокирован, потому что он похож на " + "попытку выманить секрет." + ) + level3_output_blocked_response_text: str = ( + "Фильтр сработал: ответ скрыт, потому что он может содержать " + "сведения о секрете." + ) + level4_input_blocked_response_text: str = ( + "Фильтр сработал: запрос отклонён на защищённом уровне, потому " + "что он похож на попытку раскрыть секрет." + ) + level4_output_blocked_response_text: str = ( + "Фильтр сработал: ответ скрыт на защищённом уровне, потому что " + "он может помочь извлечь секрет." ) password_words: list[str] = Field(min_length=20) diff --git a/backend/tests/test_agents.py b/backend/tests/test_agents.py index d4c753d..1f6b16f 100644 --- a/backend/tests/test_agents.py +++ b/backend/tests/test_agents.py @@ -45,7 +45,26 @@ def build_settings(rotation_interval: int = 5) -> AppSettings: }, "game": { "hard_mode_rotation_interval": rotation_interval, - "blocked_response_text": "Фильтр сработал. Ответ скрыт.", + "level2_output_blocked_response_text": ( + "Фильтр сработал: ответ агента скрыт, потому что он может " + "раскрывать секрет." + ), + "level3_input_blocked_response_text": ( + "Фильтр сработал: запрос заблокирован, потому что он похож на " + "попытку выманить секрет." + ), + "level3_output_blocked_response_text": ( + "Фильтр сработал: ответ скрыт, потому что он может содержать " + "сведения о секрете." + ), + "level4_input_blocked_response_text": ( + "Фильтр сработал: запрос отклонён на защищённом уровне, " + "потому что он похож на попытку раскрыть секрет." + ), + "level4_output_blocked_response_text": ( + "Фильтр сработал: ответ скрыт на защищённом уровне, потому " + "что он может помочь извлечь секрет." + ), "password_words": ["apple"] * 20, }, } @@ -127,7 +146,7 @@ def test_level3_blocks_on_input_before_agent_call() -> None: assert ( result.response_text - == "Фильтр сработал: запрос заблокирован, потому что он похож" + == "Фильтр сработал: запрос заблокирован, потому что он похож " "на попытку выманить секрет." ) assert result.filter_request is not None diff --git a/backend/tests/test_config.py b/backend/tests/test_config.py index a514400..af12f4d 100644 --- a/backend/tests/test_config.py +++ b/backend/tests/test_config.py @@ -16,7 +16,11 @@ api_key = "super-secret" [game] hard_mode_rotation_interval = 3 -blocked_response_text = "blocked" +level2_output_blocked_response_text = "blocked-2-out" +level3_input_blocked_response_text = "blocked-3-in" +level3_output_blocked_response_text = "blocked-3-out" +level4_input_blocked_response_text = "blocked-4-in" +level4_output_blocked_response_text = "blocked-4-out" password_words = [ "w01", "w02", "w03", "w04", "w05", "w06", "w07", "w08", "w09", "w10", diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index 88417bb..9f9e32b 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -19,7 +19,11 @@ reload = false [game] hard_mode_rotation_interval = 5 -blocked_response_text = "blocked" +level2_output_blocked_response_text = "blocked-2-out" +level3_input_blocked_response_text = "blocked-3-in" +level3_output_blocked_response_text = "blocked-3-out" +level4_input_blocked_response_text = "blocked-4-in" +level4_output_blocked_response_text = "blocked-4-out" password_words = [ "w01", "w02", "w03", "w04", "w05", "w06", "w07", "w08", "w09", "w10",