Files
microdao-daarion/tests/test_behavior_policy.py
Apple 134c044c21 feat: Behavior Policy v1 - Silent-by-default + Short-first + Media-no-comment
NODA1 agents now:
- Don't respond to broadcasts/posters/announcements without direct mention
- Don't respond to media (photo/link) without explicit question
- Keep responses short (1-2 sentences by default)
- No emoji, no "ready to help", no self-promotion

Added:
- behavior_policy.py: detect_directed_to_agent(), detect_broadcast_intent(), should_respond()
- behavior_policy_v1.txt: unified policy block for all prompts
- Pre-LLM check in http_api.py: skip Router call if should_respond=False
- NO_OUTPUT handling: don't send to Telegram if LLM returns empty
- Updated all 9 agent prompts with Behavior Policy v1
- Unit and E2E tests for 5 acceptance cases
2026-02-04 09:03:14 -08:00

448 lines
14 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Tests for Behavior Policy v1: Silent-by-default + Short-first + Media-no-comment
"""
import pytest
import sys
from pathlib import Path
# Add gateway-bot to path
sys.path.insert(0, str(Path(__file__).parent.parent / "gateway-bot"))
from behavior_policy import (
detect_agent_mention,
detect_any_agent_mention,
detect_command,
detect_question,
detect_imperative,
detect_broadcast_intent,
detect_short_note,
detect_media_question,
analyze_message,
should_respond,
is_no_output_response,
NO_OUTPUT,
TRAINING_GROUP_IDS,
)
# ========================================
# Unit Tests: detect_agent_mention
# ========================================
class TestDetectAgentMention:
def test_helion_mention_exact(self):
assert detect_agent_mention("Helion, що ти думаєш?", "helion") is True
def test_helion_mention_lowercase(self):
assert detect_agent_mention("helion допоможи", "helion") is True
def test_helion_mention_ukrainian(self):
assert detect_agent_mention("Хеліон, як справи?", "helion") is True
def test_helion_mention_at(self):
assert detect_agent_mention("@energyunionBot глянь", "helion") is True
def test_helion_no_mention(self):
assert detect_agent_mention("Привіт всім", "helion") is False
def test_daarwizz_mention(self):
assert detect_agent_mention("@DAARWIZZBot поясни", "daarwizz") is True
def test_daarwizz_no_mention(self):
assert detect_agent_mention("Helion допоможи", "daarwizz") is False
class TestDetectAnyAgentMention:
def test_helion_detected(self):
assert detect_any_agent_mention("Helion, скажи") == "helion"
def test_daarwizz_detected(self):
assert detect_any_agent_mention("@DAARWIZZBot допоможи") == "daarwizz"
def test_no_agent(self):
assert detect_any_agent_mention("Привіт всім") is None
# ========================================
# Unit Tests: detect_command
# ========================================
class TestDetectCommand:
def test_ask_command(self):
assert detect_command("/ask що таке DAO?") is True
def test_helion_command(self):
assert detect_command("/helion покажи") is True
def test_brand_command(self):
assert detect_command("/бренд_інтейк https://example.com") is True
def test_no_command(self):
assert detect_command("Привіт, як справи?") is False
def test_slash_in_middle(self):
assert detect_command("Дивись https://example.com/path") is False
# ========================================
# Unit Tests: detect_question
# ========================================
class TestDetectQuestion:
def test_question_mark(self):
assert detect_question("Що це таке?") is True
def test_question_word_start(self):
assert detect_question("Як це працює") is True
def test_question_word_чому(self):
assert detect_question("Чому так") is True
def test_english_question(self):
assert detect_question("What is this?") is True
def test_no_question(self):
assert detect_question("Добре") is False
def test_statement(self):
assert detect_question("Я згоден з цим") is False
# ========================================
# Unit Tests: detect_imperative
# ========================================
class TestDetectImperative:
def test_поясни(self):
assert detect_imperative("Поясни мені це") is True
def test_зроби(self):
assert detect_imperative("Зроби аналіз") is True
def test_допоможи(self):
assert detect_imperative("Допоможи з цим") is True
def test_after_mention(self):
assert detect_imperative("@Helion поясни") is True
def test_no_imperative(self):
assert detect_imperative("Привіт") is False
# ========================================
# Unit Tests: detect_broadcast_intent
# ========================================
class TestDetectBroadcastIntent:
def test_time_pattern(self):
assert detect_broadcast_intent("20:00 Вебінар") is True
def test_time_date_pattern(self):
assert detect_broadcast_intent("14.30 10.02 Зустріч") is True
def test_emoji_start(self):
assert detect_broadcast_intent("✅ Завершено") is True
def test_url_only(self):
assert detect_broadcast_intent("https://example.com") is True
def test_announcement_word(self):
assert detect_broadcast_intent("Анонс: новий реліз") is True
def test_normal_message(self):
assert detect_broadcast_intent("Привіт, як справи?") is False
# ========================================
# Unit Tests: detect_short_note
# ========================================
class TestDetectShortNote:
def test_checkmark_only(self):
assert detect_short_note("") is True
def test_time_checkmark(self):
assert detect_short_note("20:00 ✅") is True
def test_ok(self):
assert detect_short_note("ok") is True
def test_plus(self):
assert detect_short_note("+") is True
def test_normal_message(self):
assert detect_short_note("Привіт, як справи?") is False
def test_empty(self):
assert detect_short_note("") is True
# ========================================
# Unit Tests: detect_media_question
# ========================================
class TestDetectMediaQuestion:
def test_question_in_caption(self):
assert detect_media_question("Що на цьому фото?") is True
def test_imperative_in_caption(self):
assert detect_media_question("Опиши це зображення") is True
def test_no_question(self):
assert detect_media_question("") is False
def test_just_hashtag(self):
assert detect_media_question("#photo") is False
# ========================================
# Unit Tests: is_no_output_response
# ========================================
class TestIsNoOutputResponse:
def test_empty_string(self):
assert is_no_output_response("") is True
def test_whitespace(self):
assert is_no_output_response(" ") is True
def test_no_output_marker(self):
assert is_no_output_response("__NO_OUTPUT__") is True
def test_no_output_lowercase(self):
assert is_no_output_response("no_output") is True
def test_normal_response(self):
assert is_no_output_response("Ось моя відповідь") is False
def test_dots_only(self):
assert is_no_output_response("...") is True
# ========================================
# Integration Tests: analyze_message / should_respond
# ========================================
class TestAnalyzeMessage:
"""Test main decision logic"""
def test_training_group_always_respond(self):
decision = analyze_message(
text="Привіт всім",
agent_id="helion",
chat_id="-1003556680911", # Training group
)
assert decision.should_respond is True
assert decision.reason == "training_group"
def test_private_chat_always_respond(self):
decision = analyze_message(
text="Привіт",
agent_id="helion",
chat_id="123456",
is_private_chat=True,
)
assert decision.should_respond is True
assert decision.reason == "private_chat"
def test_direct_mention_respond(self):
decision = analyze_message(
text="Helion, що думаєш?",
agent_id="helion",
chat_id="group123",
)
assert decision.should_respond is True
assert decision.reason == "direct_mention"
def test_command_respond(self):
decision = analyze_message(
text="/helion допоможи",
agent_id="helion",
chat_id="group123",
)
assert decision.should_respond is True
assert decision.reason == "command"
def test_broadcast_no_mention_silent(self):
decision = analyze_message(
text="20:00 Вебінар Energy Union",
agent_id="helion",
chat_id="group123",
)
assert decision.should_respond is False
assert decision.reason == "broadcast_no_mention"
def test_short_note_silent(self):
decision = analyze_message(
text="20:00 10.02 ✅",
agent_id="helion",
chat_id="group123",
)
assert decision.should_respond is False
assert "short_note" in decision.reason or "broadcast" in decision.reason
def test_media_no_question_silent(self):
decision = analyze_message(
text="",
agent_id="helion",
chat_id="group123",
has_media=True,
media_caption="",
)
assert decision.should_respond is False
assert decision.reason == "media_no_question"
def test_media_with_question_respond(self):
decision = analyze_message(
text="",
agent_id="helion",
chat_id="group123",
has_media=True,
media_caption="Що на цьому фото?",
)
assert decision.should_respond is True
assert decision.reason == "media_with_question"
def test_question_no_mention_silent(self):
"""General question without mention = don't respond in groups"""
decision = analyze_message(
text="Як це працює?",
agent_id="helion",
chat_id="group123",
)
assert decision.should_respond is False
assert decision.reason == "question_no_mention"
def test_addressed_to_other_agent(self):
decision = analyze_message(
text="@DAARWIZZBot поясни DAO",
agent_id="helion",
chat_id="group123",
)
assert decision.should_respond is False
assert "other_agent" in decision.reason
# ========================================
# E2E Test Cases (from requirements)
# ========================================
class TestE2ECases:
"""
Test cases from the requirements document.
"""
def test_case_1_poster_no_question(self):
"""Case 1: Постер у каналі без питання → (нічого)"""
respond, reason = should_respond(
text="",
agent_id="helion",
chat_id="channel123",
has_media=True,
media_caption="",
)
assert respond is False
assert reason == "media_no_question"
def test_case_2_timing_no_question(self):
"""Case 2: Таймінг без питання → (нічого)"""
respond, reason = should_respond(
text="20:00 10.02 ✅",
agent_id="helion",
chat_id="group123",
)
assert respond is False
# Either short_note or broadcast pattern matches
def test_case_3_direct_request_with_photo(self):
"""Case 3: @Helion що на цьому постері? коротко + image → respond"""
respond, reason = should_respond(
text="",
agent_id="helion",
chat_id="group123",
has_media=True,
media_caption="@Helion що на цьому постері? коротко",
)
# Should respond because there's a question in caption
assert respond is True
assert reason == "media_with_question"
def test_case_4_link_no_question(self):
"""Case 4: Посилання без питання → (нічого)"""
respond, reason = should_respond(
text="https://t.me/energyunionofficial/123",
agent_id="helion",
chat_id="group123",
has_media=True, # Link treated as media
media_caption="https://t.me/energyunionofficial/123",
)
assert respond is False
assert reason == "media_no_question"
def test_case_5_link_with_question(self):
"""Case 5: @DAARWIZZ глянь посилання і скажи 3 тези + link → respond"""
respond, reason = should_respond(
text="@DAARWIZZBot глянь посилання і скажи 3 тези https://example.com",
agent_id="daarwizz",
chat_id="group123",
has_media=True,
media_caption="@DAARWIZZBot глянь посилання і скажи 3 тези https://example.com",
)
# Should respond because there's a direct mention + question
assert respond is True
assert reason == "media_with_question"
# ========================================
# Edge Cases
# ========================================
class TestEdgeCases:
def test_empty_text(self):
respond, reason = should_respond(
text="",
agent_id="helion",
chat_id="group123",
)
assert respond is False
def test_none_text(self):
respond, reason = should_respond(
text=None,
agent_id="helion",
chat_id="group123",
)
assert respond is False
def test_mixed_agents_mention(self):
"""When multiple agents mentioned, each should handle their own"""
# Helion should respond to Helion mention
respond_helion, _ = should_respond(
text="Helion та DAARWIZZ, допоможіть",
agent_id="helion",
chat_id="group123",
)
assert respond_helion is True
# DAARWIZZ should also respond
respond_daarwizz, _ = should_respond(
text="Helion та DAARWIZZ, допоможіть",
agent_id="daarwizz",
chat_id="group123",
)
assert respond_daarwizz is True
def test_question_to_specific_agent(self):
"""Question directed to another agent"""
respond, reason = should_respond(
text="@greenfoodliveBot як справи?",
agent_id="helion",
chat_id="group123",
)
assert respond is False
assert "other_agent" in reason
if __name__ == "__main__":
pytest.main([__file__, "-v"])