ForcePilot/backend/package/yuxi/channels/adapters/msteams/debounce.py
Kris bd60c15df0 feat(msteams): 新增完整的 Microsoft Teams 适配器模块
实现了 Teams 机器人所需的全功能组件,包括:
- 基础命令解析与帮助卡片生成
- 租户验证与访问控制
- 自定义 UA 与媒体工具
- 消息分块、批注处理与会话管理
- 防抖、缓存与配置路由能力
- 投票、配对、审计与运行时状态管理
- TTS 语音合成与卡片构建工具
- 群组管理与权限控制逻辑
2026-05-12 00:46:44 +08:00

73 lines
2.3 KiB
Python

"""Microsoft Teams 入站消息防抖。
相同 sender + conversation 连续消息文本合并,
减少 Agent 调用次数,提升响应效率。
"""
from __future__ import annotations
import time
from typing import Any
DEBOUNCE_TTL_SECONDS = 3.0
DEBOUNCE_MAX_TEXT_LENGTH = 4000
class DebounceManager:
def __init__(self, ttl: float = DEBOUNCE_TTL_SECONDS):
self._ttl = ttl
self._pending: dict[str, dict[str, Any]] = {}
@staticmethod
def make_key(app_id: str, conversation_id: str, sender_id: str) -> str:
return f"msteams:{app_id}:{conversation_id}:{sender_id}"
def merge(self, key: str, text: str, metadata: dict[str, Any] | None = None) -> dict[str, Any] | None:
now = time.monotonic()
existing = self._pending.get(key)
if existing and (now - existing["timestamp"]) <= self._ttl:
merged_text = existing["text"] + "\n" + text
if len(merged_text) > DEBOUNCE_MAX_TEXT_LENGTH:
merged_text = merged_text[:DEBOUNCE_MAX_TEXT_LENGTH]
self._pending[key] = {
"text": merged_text,
"timestamp": now,
"merge_count": existing.get("merge_count", 1) + 1,
"metadata": {**(existing.get("metadata") or {}), **(metadata or {})},
}
return None
self._pending[key] = {
"text": text,
"timestamp": now,
"merge_count": 1,
"metadata": metadata or {},
}
self._cleanup(now)
return self._pending[key]
def get(self, key: str) -> dict[str, Any] | None:
return self._pending.get(key)
def consume(self, key: str) -> dict[str, Any] | None:
entry = self._pending.pop(key, None)
if not entry:
return None
return {
"text": entry["text"],
"merge_count": entry["merge_count"],
"metadata": entry["metadata"],
}
def _cleanup(self, now: float) -> None:
expired = [k for k, v in self._pending.items() if now - v["timestamp"] > self._ttl * 2]
for k in expired:
self._pending.pop(k, None)
def clear(self) -> None:
self._pending.clear()
@property
def pending_count(self) -> int:
return len(self._pending)