e-261015222515: память разговора в контексте тика — секция НЕДАВНИЙ РАЗГОВОР (отвеченные реплики + наш ответ тех же чатов, cap 12) рядом с батчем неотвеченных; мин-уровень больше не теряет нить после ReplySent; тест +6 PASS

This commit is contained in:
Simbiot Harness 2026-10-15 23:02:41 +00:00
parent 2432b6f012
commit 0daf10135c
2 changed files with 53 additions and 0 deletions

View file

@ -380,6 +380,7 @@ def build_context(level_cfg, model: dict, batch=None):
if m.get("reply_to_message_id") is not None else ""))
+ ": " + m.get('text','')[:1200])
batch_ids = ",".join(ids)
chat_ids = {m.get("chat_id") for m in inbox if m.get("chat_id") is not None}
# t-261013131228: если в батче есть reply на сообщение без текста —
# добавляем последние документы этого чата с caption, чтобы триаж
# видел, о каком документе речь, даже если «голова» старше капа инбокса
@ -395,6 +396,29 @@ def build_context(level_cfg, model: dict, batch=None):
+ "; ".join(docs))
if docs_ctx:
parts.append("КОНТЕКСТ ДОКУМЕНТОВ ЧАТА:\n" + "\n".join(docs_ctx.values()))
# t-261015222515: память разговора — рядом с батчем неотвеченных лежит
# нить недавнего диалога (отвеченные реплики + наши ответы) тех же
# чатов. Раньше после ReplySent входящее выпадало из контекста тика —
# минутный уровень отвечал без памяти о разговоре (Alex про SPA-договор:
# «только что же обсуждали»). Источник — та же проекция инбокса
# (reply_text из ReplySent), отдельный стор не нужен.
replied_hist = [m for m in (model.get("inbox") or {}).values()
if m.get("replied") and not m.get("ignored")
and m.get("chat_id") in chat_ids
and (m.get("text") or "").strip()]
if replied_hist:
replied_hist = sorted(replied_hist, key=lambda x: x.get("ts") or "")[-12:]
hist_lines = []
for m in replied_hist:
m_id = "m-" + str(m.get("update_id"))
who = m.get("username") or m.get("first_name") or m.get("user_id")
line = f"- [{m_id}] {who}: {(m.get('text') or '')[:600]}"
rt = (m.get("reply_text") or "").strip()
if rt:
line += f"\n └ наш ответ: {rt[:600]}"
hist_lines.append(line)
parts.append("НЕДАВНИЙ РАЗГОВОР (уже отвечено; это память тика — НЕ отвечай "
"на них снова, используй как контекст):\n" + "\n".join(hist_lines))
# t-260930064924: батч-триаж — ВСЕ неотвеченные обрабатываются ОДНИМ тиком.
# Раньше мин-уровень отвечал по одному за тик (m-516173222..228 — 7 тиков
# подряд на один разговор). Теперь в контексте лежит ГОТОВАЯ команда

View file

@ -67,6 +67,35 @@ check("build_context: ГОТОВАЯ команда со ВСЕМИ id",
f"cli.py tg-reply {ids_line} -c" in sec, sec[:400])
check("build_context: инструкция «за ЭТОТ тик»", "именно в ЭТОМ тике" in sec)
# ── 1b. t-261015222515: память разговора — отвеченные реплики тех же чатов ──
# Сценарий эскалации: обсуждали SPA-договор, мин-тик ответил (ReplySent) —
# на следующем тике контекст ДОЛЖЕН помнить нить (текст + наш ответ),
# иначе триаж отвечает без памяти о разговоре (Alex: «только что же обсуждали»).
fake["inbox"]["m-70100"] = {
"update_id": 70100, "chat_id": 42, "text": "вот этот SPA-договор мы только что обсуждали",
"kind": "text", "replied": True, "ignored": False,
"reply_text": "да, это вторая версия без ЗП-обязательств",
"ts": "2026-09-30T06:55:00+00:00",
}
fake["inbox"]["m-70101"] = {
"update_id": 70101, "chat_id": 7, "text": "сообщение ДРУГОГО чата — не должно попасть",
"kind": "text", "replied": True, "ignored": False,
"ts": "2026-09-30T06:56:00+00:00",
}
ctx = build_context(LEVELS["min"], fake)
hist = re.search(r"НЕДАВНИЙ РАЗГОВОР.*?(?:\n\n|$)", ctx, re.S)
hsec = hist.group(0) if hist else ""
check("память: секция НЕДАВНИЙ РАЗГОВОР есть", bool(hist), ctx[:200])
check("память: отвеченное сообщение в контексте", "SPA-договор" in hsec, hsec[:200])
check("память: наш ответ виден", "без ЗП-обязательств" in hsec, hsec[:300])
check("память: чужой чат НЕ в памяти", "ДРУГОГО чата" not in hsec and "m-70101" not in hsec)
check("память: помечена «НЕ отвечай снова»", "НЕ отвечай" in hsec)
# без неотвеченных (нет батча) — секции нет: нечего контекстуализировать
only_replied = {"inbox": {"m-70100": dict(fake["inbox"]["m-70100"])},
"tasks": {}, "escalations": {}, "policy": {}}
check("память: без неотвеченных секции нет",
"НЕДАВНИЙ РАЗГОВОР" not in build_context(LEVELS["min"], only_replied))
# ── 2. send_batch_reply: одна отправка → ReplySent на каждое входящее ──
# сценарий из постановки: 7 сообщений одного разговора об экономии тиков
for i, text in enumerate([