адресация: имя бота — только звательное обращение; разговор О Коди — не повод отвечать
Кейс 2026-10-17 07:40: фильтр отсёк «Блять)»/«Продолжает)», но правило «имя в тексте = обращение» ответило на «Так теперь Коди должен отвечать когда его тегают…» и «Ещё и забыл что он Коди)» — люди обсуждали бота. - детерминированно — только звательное: «Коди, …», «Коди!», «…, Коди»; имя посреди фразы — серая зона; - промпт decision-модели: «Коди» = Cody, речь о нём в третьем лице — не обращение. Пробы: 8/8 (о боте ≤ 0.14, к боту ≥ 0.96); реплей истории правления — оба утренних ложных ответа ушли (0.40/0.13), разговор Alex с ботом без тега по-прежнему распознаётся; - мандат мин-уровня: «Коди» в чате — это ты (бот писал о себе в третьем лице: «Коди … на вашей стороне»). Тест: test_addressing 31 PASS; полный набор — без регрессий. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3ff504143a
commit
16d2b438bd
3 changed files with 27 additions and 7 deletions
|
|
@ -41,9 +41,16 @@ def is_group(m: dict) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def _names_re():
|
def _names_re():
|
||||||
|
"""Обращение по имени — только звательное, с пунктуацией: «Коди, …»,
|
||||||
|
«Коди!», «…, Коди», просто «Коди». Имя посреди фразы («Коди должен
|
||||||
|
отвечать…», «он Коди») — разговор О боте, его решает модель (кейс
|
||||||
|
2026-10-17: правило «имя где угодно» отвечало на обсуждение бота)."""
|
||||||
names = os.environ.get("SIMBIOT_BOT_NAMES", "коди,cody,juno,джуно")
|
names = os.environ.get("SIMBIOT_BOT_NAMES", "коди,cody,juno,джуно")
|
||||||
alts = "|".join(re.escape(n.strip()) for n in names.split(",") if n.strip())
|
a = "|".join(re.escape(n.strip()) for n in names.split(",") if n.strip())
|
||||||
return re.compile(rf"(?<![\w@])({alts})(?!\w)", re.IGNORECASE)
|
return re.compile(
|
||||||
|
rf"^\s*(?:{a})\s*(?:[,!:?]|$)" # «Коди, …» / «Коди!» / «Коди»
|
||||||
|
rf"|,\s*(?:{a})\s*[!?.)]*\s*$", # «…, Коди»
|
||||||
|
re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
def _min_p() -> float:
|
def _min_p() -> float:
|
||||||
|
|
@ -96,8 +103,8 @@ def model_verdict(m: dict, inbox: dict):
|
||||||
hist = _history(m, inbox)
|
hist = _history(m, inbox)
|
||||||
who = m.get("first_name") or m.get("username") or "?"
|
who = m.get("first_name") or m.get("username") or "?"
|
||||||
people = sorted({x.split(":", 1)[0] for x in hist if not x.startswith("Cody (bot)")} - {who})
|
people = sorted({x.split(":", 1)[0] for x in hist if not x.startswith("Cody (bot)")} - {who})
|
||||||
crit = {"bot": "Cody, the AI assistant bot: the message asks Cody to answer, "
|
crit = {"bot": "Cody (Коди), the AI assistant bot: the message speaks TO Cody — "
|
||||||
"explain or do something"}
|
"asks Cody to answer, explain or do something"}
|
||||||
for nm in people[:6]:
|
for nm in people[:6]:
|
||||||
crit["p_" + nm] = f"{nm} (a human in the chat)"
|
crit["p_" + nm] = f"{nm} (a human in the chat)"
|
||||||
crit["group"] = ("the group in general / nobody specific (greetings, remarks, "
|
crit["group"] = ("the group in general / nobody specific (greetings, remarks, "
|
||||||
|
|
@ -105,7 +112,10 @@ def model_verdict(m: dict, inbox: dict):
|
||||||
rt = (f" [in reply to: {(m.get('reply_to_text') or m.get('reply_to_desc') or '')[:150]}]"
|
rt = (f" [in reply to: {(m.get('reply_to_text') or m.get('reply_to_desc') or '')[:150]}]"
|
||||||
if m.get("reply_to_message_id") else "")
|
if m.get("reply_to_message_id") else "")
|
||||||
state = (f"Group chat '{m.get('chat_title') or ''}': humans talk to each other; Cody "
|
state = (f"Group chat '{m.get('chat_title') or ''}': humans talk to each other; Cody "
|
||||||
"(@juno42bot) is an AI assistant bot that must speak ONLY when addressed.\n"
|
"(@juno42bot, called «Коди» in Russian) is an AI assistant bot that must "
|
||||||
|
"speak ONLY when addressed. Talking ABOUT Cody in the third person "
|
||||||
|
"(«Коди должен…», «он Коди», «Коди стал…») is people discussing the bot "
|
||||||
|
"with each other — NOT addressing Cody.\n"
|
||||||
"Recent messages:\n" + "\n".join(hist) +
|
"Recent messages:\n" + "\n".join(hist) +
|
||||||
f"\nLAST message — {who}{rt}: "
|
f"\nLAST message — {who}{rt}: "
|
||||||
f"{(m.get('text') or '[' + (m.get('kind') or '?') + ']')[:600]}")
|
f"{(m.get('text') or '[' + (m.get('kind') or '?') + ']')[:600]}")
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,11 @@ LEVELS = {
|
||||||
"min": CycleLevel(
|
"min": CycleLevel(
|
||||||
name="min", label="Минута · пульс", every=MIN, uses_llm=True,
|
name="min", label="Минута · пульс", every=MIN, uses_llm=True,
|
||||||
model="auto", effort="low", ctx_tokens=256_000, task_classes=("S",),
|
model="auto", effort="low", ctx_tokens=256_000, task_classes=("S",),
|
||||||
mandate=("Ты — минутный пульс харнеса Simbiot. Триаж входящих TG-сообщений: "
|
mandate=("Ты — минутный пульс харнеса Simbiot. В Telegram ты — Коди "
|
||||||
|
"(@juno42bot, Cody Command): «Коди» в чате — это ТЫ, говори о себе "
|
||||||
|
"в первом лице. Из групп тебе приходят только сообщения, адресованные "
|
||||||
|
"тебе (фильтр адресации) — на них отвечай. "
|
||||||
|
"Триаж входящих TG-сообщений: "
|
||||||
"все неотвеченные сообщения приходят тебе ОДНИМ батчем (секция "
|
"все неотвеченные сообщения приходят тебе ОДНИМ батчем (секция "
|
||||||
"входящих в контексте) — обработай его ЦЕЛИКОМ за этот тик: "
|
"входящих в контексте) — обработай его ЦЕЛИКОМ за этот тик: "
|
||||||
"сгруппируй в один сводный ответ по смыслу всего разговора "
|
"сгруппируй в один сводный ответ по смыслу всего разговора "
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,14 @@ check("личка → всегда", rv({"chat_type": "private", "text": "при
|
||||||
check("@juno42bot → да", rv(g("глянь", mentions_bot=True))[0] is True)
|
check("@juno42bot → да", rv(g("глянь", mentions_bot=True))[0] is True)
|
||||||
check("reply на бота → да", rv(g("а это?", reply_to_user_id=BOT_ID, reply_to_is_bot=True))[0] is True)
|
check("reply на бота → да", rv(g("а это?", reply_to_user_id=BOT_ID, reply_to_is_bot=True))[0] is True)
|
||||||
check("имя «Коди» → да", rv(g("Коди, сделай отчёт"))[1] == "name")
|
check("имя «Коди» → да", rv(g("Коди, сделай отчёт"))[1] == "name")
|
||||||
check("«cody» латиницей → да", rv(g("cody pls"))[0] is True)
|
check("«cody» латиницей → да", rv(g("cody, pls"))[0] is True)
|
||||||
check("слово-подстрока не ловится («кодировка»)", rv(g("кодировка сломалась"))[0] is None)
|
check("слово-подстрока не ловится («кодировка»)", rv(g("кодировка сломалась"))[0] is None)
|
||||||
|
check("«сделай, Коди» → да", rv(g("сделай сводку, Коди"))[0] is True)
|
||||||
|
check("«Коди!» → да", rv(g("Коди!"))[0] is True)
|
||||||
|
check("разговор О боте → серая зона, не правило",
|
||||||
|
rv(g("Так теперь Коди должен отвечать когда его тегают")) == (None, None)
|
||||||
|
and rv(g("Ещё и забыл что он Коди)")) == (None, None)
|
||||||
|
and rv(g("Коди сейчас стал молчаливее")) == (None, None))
|
||||||
check("reply человеку → нет", rv(g("согласен", reply_to_user_id=156156021))
|
check("reply человеку → нет", rv(g("согласен", reply_to_user_id=156156021))
|
||||||
== (False, "reply_to_human"))
|
== (False, "reply_to_human"))
|
||||||
check("просто реплика → серая зона", rv(g("Доброе утро!")) == (None, None))
|
check("просто реплика → серая зона", rv(g("Доброе утро!")) == (None, None))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue