mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-14 07:53:14 +08:00
Gate group speaker isolation to shared chat sessions only.
Per-user group sessions (user_in_chat) already isolate history; keep Sender tags and focus hints only when session_scope=chat (legacy shared __group__ transcripts). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e91e4fe773
commit
9bbac6c0b8
5 changed files with 50 additions and 21 deletions
|
|
@ -1453,15 +1453,21 @@ def process_inbound_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||||
)
|
)
|
||||||
if str(lang or "").startswith("zh"):
|
if str(lang or "").startswith("zh"):
|
||||||
busy_text = (
|
busy_text = (
|
||||||
"本会话还有请求在处理,你这条已排队,完成后会继续回答。"
|
"本群共享会话还有请求在处理,你这条已排队,完成后会继续回答。"
|
||||||
if bool(getattr(inbound, "is_group", False))
|
if (
|
||||||
|
bool(getattr(inbound, "is_group", False))
|
||||||
|
and str(group_policy.session_scope or "") == "chat"
|
||||||
|
)
|
||||||
else "还在处理上一条请求,这条会排队合并处理。"
|
else "还在处理上一条请求,这条会排队合并处理。"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
busy_text = (
|
busy_text = (
|
||||||
"Still working on an earlier request in this chat; "
|
"Still working on an earlier request in this shared group chat; "
|
||||||
"your message is queued and will be answered next."
|
"your message is queued and will be answered next."
|
||||||
if bool(getattr(inbound, "is_group", False))
|
if (
|
||||||
|
bool(getattr(inbound, "is_group", False))
|
||||||
|
and str(group_policy.session_scope or "") == "chat"
|
||||||
|
)
|
||||||
else "Still working on your previous request; "
|
else "Still working on your previous request; "
|
||||||
"I'll merge this follow-up next."
|
"I'll merge this follow-up next."
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -486,11 +486,18 @@ class OclawGateway:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _group_focus_system_hint(msg: StandardMessage, lang: str) -> str:
|
def _group_focus_system_hint(msg: StandardMessage, lang: str) -> str:
|
||||||
|
"""Only for shared group transcripts (legacy session_scope=chat / __group__)."""
|
||||||
md = msg.metadata if isinstance(msg.metadata, dict) else {}
|
md = msg.metadata if isinstance(msg.metadata, dict) else {}
|
||||||
if not bool(md.get("is_group")):
|
if not bool(md.get("is_group")):
|
||||||
return ""
|
return ""
|
||||||
from runtime.orchestration.group_ingest import build_group_focus_instruction
|
from runtime.orchestration.group_ingest import (
|
||||||
|
build_group_focus_instruction,
|
||||||
|
normalize_group_session_scope,
|
||||||
|
)
|
||||||
|
|
||||||
|
scope = normalize_group_session_scope(md.get("group_session_scope"))
|
||||||
|
if scope != "chat":
|
||||||
|
return ""
|
||||||
return str(build_group_focus_instruction(lang=lang) or "").strip()
|
return str(build_group_focus_instruction(lang=lang) or "").strip()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -590,11 +590,12 @@ def prepare_group_user_text_for_model(
|
||||||
account_id=account_id,
|
account_id=account_id,
|
||||||
)
|
)
|
||||||
prefix_parts: list[str] = []
|
prefix_parts: list[str] = []
|
||||||
# Always tag the speaker in groups (shared or per-user) so history/quotes cannot 串话.
|
# Shared group transcript only: tag speaker so multi-member history cannot 串话.
|
||||||
_ = session_scope # retained for call-site compatibility / future policy forks
|
# Per-user group sessions (user_in_chat) already isolate by session — skip the prefix.
|
||||||
prefix_parts.append(
|
if normalize_group_session_scope(session_scope) == "chat":
|
||||||
build_group_sender_context(metadata=metadata, external_user_id=external_user_id, lang=lang)
|
prefix_parts.append(
|
||||||
)
|
build_group_sender_context(metadata=metadata, external_user_id=external_user_id, lang=lang)
|
||||||
|
)
|
||||||
quote = str(quoted_ctx or "").strip()
|
quote = str(quoted_ctx or "").strip()
|
||||||
if quote:
|
if quote:
|
||||||
prefix_parts.append(quote)
|
prefix_parts.append(quote)
|
||||||
|
|
@ -605,14 +606,15 @@ def prepare_group_user_text_for_model(
|
||||||
|
|
||||||
|
|
||||||
def build_group_focus_instruction(*, lang: str = "en") -> str:
|
def build_group_focus_instruction(*, lang: str = "en") -> str:
|
||||||
|
"""Isolation hint for *shared* group transcripts (session_scope=chat)."""
|
||||||
if str(lang or "").strip().lower().startswith("zh"):
|
if str(lang or "").strip().lower().startswith("zh"):
|
||||||
return (
|
return (
|
||||||
"[群聊规则:只回答当前发言人(见 [发言: …])的问题;"
|
"[群聊规则(共享会话):只回答当前发言人(见 [发言: …])的问题;"
|
||||||
"除非本条消息明确引用或承接前文,否则不要默认继承其他群成员的上下文;"
|
"除非本条消息明确引用或承接前文,否则不要默认继承其他群成员的上下文;"
|
||||||
"若排队合并了多条跟进,按条分别回应并 @ 对应发言人。]"
|
"若排队合并了多条跟进,按条分别回应并 @ 对应发言人。]"
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
"[Group chat rule: answer only the current sender (see [Sender: …]). "
|
"[Shared group-chat rule: answer only the current sender (see [Sender: …]). "
|
||||||
"Do not assume context from other members unless this message explicitly quotes or references it. "
|
"Do not assume context from other members unless this message explicitly quotes or references it. "
|
||||||
"If several follow-ups were merged while busy, answer each item and @ the matching sender.]"
|
"If several follow-ups were merged while busy, answer each item and @ the matching sender.]"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -400,8 +400,10 @@ def test_prepare_group_user_text_for_model_user_in_chat() -> None:
|
||||||
mention_names=["吴华"],
|
mention_names=["吴华"],
|
||||||
lang="zh",
|
lang="zh",
|
||||||
)
|
)
|
||||||
assert out.startswith("[发言: Bob]")
|
# Per-user group sessions already isolate by session — no sender prefix.
|
||||||
assert "每三分钟提醒@吴华 喝水" in out
|
assert out == "每三分钟提醒@吴华 喝水"
|
||||||
|
assert "[发言:" not in out
|
||||||
|
assert "[Sender:" not in out
|
||||||
|
|
||||||
|
|
||||||
def test_prepare_group_user_text_for_model_shared_chat_prefix() -> None:
|
def test_prepare_group_user_text_for_model_shared_chat_prefix() -> None:
|
||||||
|
|
@ -423,7 +425,7 @@ def test_build_group_focus_instruction() -> None:
|
||||||
en = build_group_focus_instruction()
|
en = build_group_focus_instruction()
|
||||||
zh = build_group_focus_instruction(lang="zh")
|
zh = build_group_focus_instruction(lang="zh")
|
||||||
assert "current sender" in en
|
assert "current sender" in en
|
||||||
assert "[Sender:" in en or "Sender" in en
|
assert "Shared group" in en or "shared" in en.lower()
|
||||||
assert "群聊规则" in zh
|
assert "群聊规则" in zh
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -751,8 +753,9 @@ def test_inbound_group_mention_uses_per_user_session_and_sender_prefix(
|
||||||
assert len(session_ids) == 2
|
assert len(session_ids) == 2
|
||||||
assert session_ids[0] != session_ids[1]
|
assert session_ids[0] != session_ids[1]
|
||||||
assert "[群成员:" not in captured["text"]
|
assert "[群成员:" not in captured["text"]
|
||||||
assert captured["text"].startswith("[Sender: Bob]")
|
assert "[发言:" not in captured["text"]
|
||||||
assert captured["text"].endswith("again")
|
assert "[Sender:" not in captured["text"]
|
||||||
|
assert captured["text"] == "again"
|
||||||
assert "群聊规则" not in captured["text"]
|
assert "群聊规则" not in captured["text"]
|
||||||
|
|
||||||
sid = store.get_or_create_channel_session_v2(
|
sid = store.get_or_create_channel_session_v2(
|
||||||
|
|
|
||||||
|
|
@ -956,10 +956,10 @@ def test_channel_file_delivery_hint_goes_to_system_not_user_message() -> None:
|
||||||
assert not OclawGateway._is_channel_delivery_channel(msg_admin)
|
assert not OclawGateway._is_channel_delivery_channel(msg_admin)
|
||||||
|
|
||||||
|
|
||||||
def test_group_focus_system_hint_only_for_groups() -> None:
|
def test_group_focus_system_hint_only_for_shared_group_scope() -> None:
|
||||||
from runtime.types import StandardMessage
|
from runtime.types import StandardMessage
|
||||||
|
|
||||||
group_msg = StandardMessage(
|
shared = StandardMessage(
|
||||||
session_id="s1",
|
session_id="s1",
|
||||||
tenant_id="t1",
|
tenant_id="t1",
|
||||||
user_id="u1",
|
user_id="u1",
|
||||||
|
|
@ -967,7 +967,17 @@ def test_group_focus_system_hint_only_for_groups() -> None:
|
||||||
channel="whatsapp",
|
channel="whatsapp",
|
||||||
text="@bot alarms",
|
text="@bot alarms",
|
||||||
attachments=[],
|
attachments=[],
|
||||||
metadata={"is_group": True},
|
metadata={"is_group": True, "group_session_scope": "chat"},
|
||||||
|
)
|
||||||
|
per_user = StandardMessage(
|
||||||
|
session_id="s1",
|
||||||
|
tenant_id="t1",
|
||||||
|
user_id="u1",
|
||||||
|
role="member",
|
||||||
|
channel="whatsapp",
|
||||||
|
text="@bot alarms",
|
||||||
|
attachments=[],
|
||||||
|
metadata={"is_group": True, "group_session_scope": "user_in_chat"},
|
||||||
)
|
)
|
||||||
dm = StandardMessage(
|
dm = StandardMessage(
|
||||||
session_id="s1",
|
session_id="s1",
|
||||||
|
|
@ -979,6 +989,7 @@ def test_group_focus_system_hint_only_for_groups() -> None:
|
||||||
attachments=[],
|
attachments=[],
|
||||||
metadata={"is_group": False},
|
metadata={"is_group": False},
|
||||||
)
|
)
|
||||||
hint = OclawGateway._group_focus_system_hint(group_msg, "en")
|
hint = OclawGateway._group_focus_system_hint(shared, "en")
|
||||||
assert "current sender" in hint
|
assert "current sender" in hint
|
||||||
|
assert OclawGateway._group_focus_system_hint(per_user, "en") == ""
|
||||||
assert OclawGateway._group_focus_system_hint(dm, "en") == ""
|
assert OclawGateway._group_focus_system_hint(dm, "en") == ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue