Spaces:
Running
Running
SilverElixir commited on
Commit ·
85833be
1
Parent(s): 6e8e909
Resolve declined nicknames both ways, cover fixes with tests
Browse files- lumen_chat_state.py +10 -3
- tests/test_bot_state.py +50 -8
lumen_chat_state.py
CHANGED
|
@@ -484,10 +484,14 @@ def _restore_user_names(raw: Any) -> dict[str, dict[str, str]]:
|
|
| 484 |
|
| 485 |
|
| 486 |
def _resolve_nick(state: dict[str, Any], nick: str) -> str | None:
|
| 487 |
-
"""Прозвище в показное имя: ищет по username и first_name без регистра.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
try:
|
| 489 |
needle = (nick or "").strip().lstrip("@").lower()
|
| 490 |
-
if
|
| 491 |
return None
|
| 492 |
bucket = state.get("user_names") or {}
|
| 493 |
if not isinstance(bucket, dict):
|
|
@@ -497,7 +501,10 @@ def _resolve_nick(state: dict[str, Any], nick: str) -> str | None:
|
|
| 497 |
continue
|
| 498 |
for field in ("username", "first_name"):
|
| 499 |
val = entry.get(field)
|
| 500 |
-
if isinstance(val, str)
|
|
|
|
|
|
|
|
|
|
| 501 |
return _sender_display(entry.get("username"), entry.get("first_name"))
|
| 502 |
except Exception:
|
| 503 |
pass
|
|
|
|
| 484 |
|
| 485 |
|
| 486 |
def _resolve_nick(state: dict[str, Any], nick: str) -> str | None:
|
| 487 |
+
"""Прозвище в показное имя: ищет по username и first_name без регистра.
|
| 488 |
+
|
| 489 |
+
Смотрит в обе стороны: «блин» находит «Блинчик», а «блинчика» тоже
|
| 490 |
+
находит «Блинчик» (склонения в живой речи). Короткие совпадения
|
| 491 |
+
отбрасываем, иначе двухбуквенные огрызки шумят на каждом сообщении."""
|
| 492 |
try:
|
| 493 |
needle = (nick or "").strip().lstrip("@").lower()
|
| 494 |
+
if len(needle) < 3:
|
| 495 |
return None
|
| 496 |
bucket = state.get("user_names") or {}
|
| 497 |
if not isinstance(bucket, dict):
|
|
|
|
| 501 |
continue
|
| 502 |
for field in ("username", "first_name"):
|
| 503 |
val = entry.get(field)
|
| 504 |
+
if not isinstance(val, str):
|
| 505 |
+
continue
|
| 506 |
+
low = val.lower()
|
| 507 |
+
if needle in low or (len(low) >= 4 and low in needle):
|
| 508 |
return _sender_display(entry.get("username"), entry.get("first_name"))
|
| 509 |
except Exception:
|
| 510 |
pass
|
tests/test_bot_state.py
CHANGED
|
@@ -3043,14 +3043,17 @@ def test_combined_memory_caps_background_at_100():
|
|
| 3043 |
from collections import deque
|
| 3044 |
state = {"history": [{"role": "user", "content": f"вопрос {i}"} for i in range(60)],
|
| 3045 |
"ctx": deque(), "recent_media_ids": {}, "user_names": {}}
|
| 3046 |
-
|
| 3047 |
-
|
| 3048 |
-
|
| 3049 |
-
|
| 3050 |
-
|
| 3051 |
-
|
| 3052 |
-
|
| 3053 |
-
|
|
|
|
|
|
|
|
|
|
| 3054 |
|
| 3055 |
|
| 3056 |
def test_sender_directory_resolves_nicknames():
|
|
@@ -3079,3 +3082,42 @@ def test_reply_block_quotes_replied_message():
|
|
| 3079 |
_bot.BOT_USERNAME = prev_username
|
| 3080 |
assert "прошлый ответ бота" in block
|
| 3081 |
assert _bot._reply_target_block(SimpleNamespace(reply_to_message=None), state) == ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3043 |
from collections import deque
|
| 3044 |
state = {"history": [{"role": "user", "content": f"вопрос {i}"} for i in range(60)],
|
| 3045 |
"ctx": deque(), "recent_media_ids": {}, "user_names": {}}
|
| 3046 |
+
try:
|
| 3047 |
+
for i in range(60):
|
| 3048 |
+
message = SimpleNamespace(chat=SimpleNamespace(id=9001),
|
| 3049 |
+
from_user=SimpleNamespace(id=7, username="m1st", first_name="M"),
|
| 3050 |
+
text=f"фон {i}")
|
| 3051 |
+
bot._record_passive_group_context(message, state, f"фон {i}")
|
| 3052 |
+
assert len(state["history"]) == 60
|
| 3053 |
+
assert len(state["history"]) + len(state["ctx"]) <= bot.MAX_TOTAL_MEMORY_MESSAGES
|
| 3054 |
+
assert state["ctx"][0].endswith("фон 20")
|
| 3055 |
+
finally:
|
| 3056 |
+
bot._dirty_chat_ids.discard(9001)
|
| 3057 |
|
| 3058 |
|
| 3059 |
def test_sender_directory_resolves_nicknames():
|
|
|
|
| 3082 |
_bot.BOT_USERNAME = prev_username
|
| 3083 |
assert "прошлый ответ бота" in block
|
| 3084 |
assert _bot._reply_target_block(SimpleNamespace(reply_to_message=None), state) == ""
|
| 3085 |
+
|
| 3086 |
+
|
| 3087 |
+
def test_sender_display_shows_both_names():
|
| 3088 |
+
# Ник с first_name в скобках: иначе "блинчик" терялся при наличии username.
|
| 3089 |
+
from lumen_chat_state import _sender_display
|
| 3090 |
+
assert _sender_display("The_6JluH4ik", "Блинчик") == "@The_6JluH4ik (Блинчик)"
|
| 3091 |
+
assert _sender_display("m1st", "m1st") == "@m1st"
|
| 3092 |
+
assert _sender_display(None, "Блинчик") == "Блинчик"
|
| 3093 |
+
assert _sender_display(None, None) == "User"
|
| 3094 |
+
|
| 3095 |
+
|
| 3096 |
+
def test_tagged_prompt_hints_nickname():
|
| 3097 |
+
# Прозвище из вопроса связывается с автором: модель не гадает кто это.
|
| 3098 |
+
state = {"history": [], "user_names": {}}
|
| 3099 |
+
bot._note_sender(state, 11, "The_6JluH4ik", "Блинчик")
|
| 3100 |
+
message = SimpleNamespace(chat=SimpleNamespace(id=9004),
|
| 3101 |
+
from_user=SimpleNamespace(id=7, username="m1st", first_name="M"),
|
| 3102 |
+
text="проверь слова блинчика", reply_to_message=None)
|
| 3103 |
+
prompt = bot._tagged_prompt(state, message, "проверь слова блинчика")
|
| 3104 |
+
assert "[@m1st (M)]" in prompt
|
| 3105 |
+
assert "Подсказка" in prompt and "Блинчик" in prompt
|
| 3106 |
+
|
| 3107 |
+
|
| 3108 |
+
def test_ctx_survives_serialize_restore():
|
| 3109 |
+
# Фон переживает рестарт: иначе единые 100 сообщений сбрасывались деплоем.
|
| 3110 |
+
from collections import deque
|
| 3111 |
+
cid = 9005
|
| 3112 |
+
state = {"history": [{"role": "user", "content": "вопрос"}],
|
| 3113 |
+
"ctx": deque(["@m1st (M): фоновая реплика"]),
|
| 3114 |
+
"recent_media_ids": {}, "user_names": {"7": {"username": "m1st", "first_name": "M"}},
|
| 3115 |
+
"last_activity": 1.0, "lang": "ru"}
|
| 3116 |
+
try:
|
| 3117 |
+
snapshot = bot._serialize_chat_state(state)
|
| 3118 |
+
assert snapshot["ctx"] == ["@m1st (M): фоновая реплика"]
|
| 3119 |
+
bot._restore_single_chat(cid, snapshot)
|
| 3120 |
+
assert list(bot.chat_state[cid]["ctx"]) == ["@m1st (M): фоновая реплика"]
|
| 3121 |
+
assert bot.chat_state[cid]["user_names"] == {"7": {"username": "m1st", "first_name": "M"}}
|
| 3122 |
+
finally:
|
| 3123 |
+
bot.chat_state.pop(cid, None)
|