From 16d2b438bdea57c19f25c565783703266e16dd3b Mon Sep 17 00:00:00 2001 From: Simbiot Harness Date: Sat, 17 Oct 2026 07:46:20 +0000 Subject: [PATCH] =?UTF-8?q?=D0=B0=D0=B4=D1=80=D0=B5=D1=81=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F:=20=D0=B8=D0=BC=D1=8F=20=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=20=E2=80=94=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B7?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B5;=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B3=D0=BE=D0=B2=D0=BE=D1=80=20=D0=9E=20=D0=9A?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=20=E2=80=94=20=D0=BD=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=20=D0=BE=D1=82=D0=B2=D0=B5=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кейс 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 --- harness/addressing.py | 20 +++++++++++++++----- harness/levels.py | 6 +++++- tests/test_addressing.py | 8 +++++++- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/harness/addressing.py b/harness/addressing.py index 57fa055..d840e2a 100644 --- a/harness/addressing.py +++ b/harness/addressing.py @@ -41,9 +41,16 @@ def is_group(m: dict) -> bool: def _names_re(): + """Обращение по имени — только звательное, с пунктуацией: «Коди, …», + «Коди!», «…, Коди», просто «Коди». Имя посреди фразы («Коди должен + отвечать…», «он Коди») — разговор О боте, его решает модель (кейс + 2026-10-17: правило «имя где угодно» отвечало на обсуждение бота).""" names = os.environ.get("SIMBIOT_BOT_NAMES", "коди,cody,juno,джуно") - alts = "|".join(re.escape(n.strip()) for n in names.split(",") if n.strip()) - return re.compile(rf"(? float: @@ -96,8 +103,8 @@ def model_verdict(m: dict, inbox: dict): hist = _history(m, inbox) 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}) - crit = {"bot": "Cody, the AI assistant bot: the message asks Cody to answer, " - "explain or do something"} + crit = {"bot": "Cody (Коди), the AI assistant bot: the message speaks TO Cody — " + "asks Cody to answer, explain or do something"} for nm in people[:6]: crit["p_" + nm] = f"{nm} (a human in the chat)" 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]}]" if m.get("reply_to_message_id") else "") 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) + f"\nLAST message — {who}{rt}: " f"{(m.get('text') or '[' + (m.get('kind') or '?') + ']')[:600]}") diff --git a/harness/levels.py b/harness/levels.py index 8a45da3..2cd10b8 100644 --- a/harness/levels.py +++ b/harness/levels.py @@ -64,7 +64,11 @@ LEVELS = { "min": CycleLevel( name="min", label="Минута · пульс", every=MIN, uses_llm=True, model="auto", effort="low", ctx_tokens=256_000, task_classes=("S",), - mandate=("Ты — минутный пульс харнеса Simbiot. Триаж входящих TG-сообщений: " + mandate=("Ты — минутный пульс харнеса Simbiot. В Telegram ты — Коди " + "(@juno42bot, Cody Command): «Коди» в чате — это ТЫ, говори о себе " + "в первом лице. Из групп тебе приходят только сообщения, адресованные " + "тебе (фильтр адресации) — на них отвечай. " + "Триаж входящих TG-сообщений: " "все неотвеченные сообщения приходят тебе ОДНИМ батчем (секция " "входящих в контексте) — обработай его ЦЕЛИКОМ за этот тик: " "сгруппируй в один сводный ответ по смыслу всего разговора " diff --git a/tests/test_addressing.py b/tests/test_addressing.py index d66ac73..6edbe96 100644 --- a/tests/test_addressing.py +++ b/tests/test_addressing.py @@ -48,8 +48,14 @@ check("личка → всегда", rv({"chat_type": "private", "text": "при 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("имя «Коди» → да", 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 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)) == (False, "reply_to_human")) check("просто реплика → серая зона", rv(g("Доброе утро!")) == (None, None))