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))