ForcePilot/backend/package/yuxi/channels/contract/policy/config_schema.py
Kris b8ac375e8e feat: 新增多渠道客服会话、身份合并与重试能力等功能
本次提交包含多项核心功能迭代与优化:
1. 新增KF客服会话类型,完善聊天类型枚举
2. 新增消息撤回操作类型与身份置信度排序方法
3. 新增控制面结果DTO与敏感字段注册表端口
4. 新增身份合并回滚、重试失败投递目标等业务能力
5. 优化Outbox投递逻辑与熔断器状态判断
6. 修复部分代码冗余与类型不匹配问题
7. 新增数据库索引并发创建与路由绑定清理逻辑
8. 优化会话关闭服务与插件重载并发控制
2026-07-09 04:21:28 +08:00

752 lines
27 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""配置 schema 元数据契约层定义。
定义配置字段的完整 schema 元数据(``CONFIG_SCHEMA``),包括键、类型、
是否必填、默认值、是否可热更新与约束,作为配置 schema 的唯一真相源
§13.1 配置分层 / §6.1 契约层职责)。适配器(如 ``RedisConfigAdapter``
与应用层(如 ``ConfigManager``)共享引用本模块,消除 schema 元数据
下沉至适配器层的 DRY 违反。
``HOT_RELOADABLE_KEYS`` / ``NON_HOT_RELOADABLE_KEYS`` 从 ``CONFIG_SCHEMA``
派生,供 ``ConfigManager`` 做热更新策略校验ADP-030
"""
from __future__ import annotations
from yuxi.channels.contract.dtos.config import ConfigField, ConfigScope
# 配置 schema全部配置字段的完整元数据FR-37 配置热更新)。
# 48 个可热更新字段 + 14 个不可热更新字段,共 62 个。
# 字段顺序先可热更新hot_reloadable=True后不可热更新hot_reloadable=False
CONFIG_SCHEMA: tuple[ConfigField, ...] = (
# === 可热更新字段hot_reloadable=True===
ConfigField(
key="dm_policy",
type="json",
required=False,
default="allow",
hot_reloadable=True,
title="私信策略",
description="控制私信/群聊的自动回复与路由策略,如是否允许陌生人私信、是否自动创建会话等。",
category="消息策略",
),
ConfigField(
key="allow_from",
type="json",
required=False,
default=None,
hot_reloadable=True,
title="来源白名单",
description="允许向当前账户发起对话的对端白名单,未配置时不限制。",
category="访问控制",
),
ConfigField(
key="rate_limit",
type="json",
required=False,
default=None,
hot_reloadable=True,
title="消息限流",
description="控制账户级别的消息发送频率限制,防止滥用或触发渠道风控。",
category="限流与预算",
),
ConfigField(
key="bot_loop_budget",
type="json",
required=False,
default={"max_replies_per_hour": 20, "cooldown_seconds": 60},
hot_reloadable=True,
title="Bot 循环预算",
description=(
"Bot 循环预算配置,包含 max_replies_per_hour"
"每小时最大回复数≤0 表示不限制)和 cooldown_seconds冷却秒数"
),
category="限流与预算",
),
# FR-08~14 功能开关Task 30
ConfigField(
key="rich_message_enabled",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="富消息开关",
description="是否启用富媒体消息(卡片、按钮等)发送能力。",
category="功能开关",
),
ConfigField(
key="status_writeback_enabled",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="状态回写开关",
description="是否将渠道用户状态(在线/离开等)回写到知识库。",
category="功能开关",
),
ConfigField(
key="agent_prompt_enabled",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="Agent 提示开关",
description="是否在消息中附加 Agent 提示上下文。",
category="功能开关",
),
ConfigField(
key="channel_tools_enabled",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="渠道工具开关",
description="是否允许 Agent 调用渠道相关工具。",
category="功能开关",
),
# AC-22: 消息操作开关默认"开"FR-12
ConfigField(
key="message_ops_enabled",
type="bool",
required=False,
default=True,
hot_reloadable=True,
title="消息操作开关",
description="总开关:是否启用消息操作(反应、置顶、卡片更新等)。",
category="功能开关",
),
ConfigField(
key="streaming_enabled",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="流式响应开关",
description="是否启用流式(打字机效果)响应输出。",
category="功能开关",
),
ConfigField(
key="enable_typing",
type="bool",
required=False,
default=True,
hot_reloadable=True,
title="输入中提示开关",
description="是否向用户展示'正在输入'状态提示。",
category="功能开关",
),
ConfigField(
key="directory_enabled",
type="bool",
required=False,
default=True,
hot_reloadable=True,
title="目录服务开关",
description="是否启用渠道目录(用户/群组列表)查询能力。",
category="功能开关",
),
ConfigField(
key="command_enabled",
type="bool",
required=False,
default=True,
hot_reloadable=True,
title="命令处理开关",
description="是否启用以 command_prefix 开头的命令解析与处理。",
category="功能开关",
),
# FR-12 消息操作细粒度开关PRD §FR-12 配置项)。
# AC-22 默认行为:表情反应"开"、置顶"关"、卡片更新"关"。
ConfigField(
key="enable_reaction",
type="bool",
required=False,
default=True,
hot_reloadable=True,
title="表情反应开关",
description="是否允许对消息添加表情反应。",
category="功能开关",
),
ConfigField(
key="enable_pin",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="消息置顶开关",
description="是否允许置顶/取消置顶消息。",
category="功能开关",
),
ConfigField(
key="enable_card_update",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="卡片更新开关",
description="是否允许更新已发送的卡片消息。",
category="功能开关",
),
# FR-15 管理员命令权限白名单账户级JSON 数组,存放允许执行 ADMIN 权限命令的 peer_id
ConfigField(
key="admin_users",
type="json",
required=False,
default=None,
hot_reloadable=True,
scope=ConfigScope.ACCOUNT,
title="管理员白名单",
description="允许执行 ADMIN 权限命令的 peer_id 列表,账户级配置。",
category="身份与权限",
),
# FR-29 入站提及策略账户级JSON含 allowed_implicit_kinds 列表,
# 控制哪些隐式提及类型被视为"已提及",未配置时默认空策略)
ConfigField(
key="mention_policy",
type="json",
required=False,
default=None,
hot_reloadable=True,
scope=ConfigScope.ACCOUNT,
title="提及策略",
description="控制哪些隐式提及类型被视为'已提及',影响命令触发与会话路由。",
category="消息策略",
),
# FR-07 会话合并策略开关(默认禁用)
ConfigField(
key="merge_strategy_enabled",
type="bool",
required=False,
default=False,
hot_reloadable=True,
title="会话合并开关",
description="是否启用按策略自动合并会话。",
category="会话策略",
),
# FR-06 跨渠道身份策略(默认隔离,可选 association 开启跨渠道关联)
ConfigField(
key="cross_channel_identity_strategy",
type="str",
required=False,
default="isolation",
hot_reloadable=True,
title="跨渠道身份策略",
description="跨渠道身份关联策略isolation隔离或 association关联",
category="身份与权限",
),
# 可配置值Task 30
ConfigField(
key="command_prefix",
type="str",
required=False,
default="/",
hot_reloadable=True,
title="命令前缀",
description="命令触发前缀,默认为 '/'",
category="命令与交互",
),
ConfigField(
key="fence_ttl_seconds",
type="int",
required=False,
default=120,
hot_reloadable=True,
title="围栏 TTL",
description="对话围栏fence的默认过期时间",
category="命令与交互",
),
ConfigField(
key="streaming_min_chunk_interval_ms",
type="int",
required=False,
default=200,
hot_reloadable=True,
title="流式最小间隔",
description="流式响应相邻分块之间的最小间隔(毫秒)。",
category="流式与传输体验",
),
ConfigField(
key="streaming_ttl_seconds",
type="int",
required=False,
default=60,
hot_reloadable=True,
title="流式 TTL",
description="流式响应状态的最大保留时间(秒)。",
category="流式与传输体验",
),
ConfigField(
key="typing_ttl_ms",
type="int",
required=False,
default=10000,
hot_reloadable=True,
title="输入提示 TTL",
description="'正在输入'提示状态的最大持续时间(毫秒)。",
category="流式与传输体验",
),
ConfigField(
key="durability_policy_default",
type="str",
required=False,
default="required",
hot_reloadable=True,
title="默认持久化策略",
description="消息默认持久化策略required / best_effort / none。",
category="可靠性",
),
# FR-24 分阶段 ACK 策略PRD §FR-24
# 四阶段策略after_record / after_agent_dispatch / after_persist / manual
# 默认 after_agent_dispatch平衡可靠性与响应速度
ConfigField(
key="ack_policy",
type="str",
required=False,
default="after_agent_dispatch",
hot_reloadable=True,
title="ACK 策略",
description="分阶段 ACK 策略after_record / after_agent_dispatch / after_persist / manual。",
category="可靠性",
),
# 手动策略下插件忘记 ACK 时核心兜底超时(默认 60s自动 ACK。
ConfigField(
key="ack_fallback_timeout_seconds",
type="int",
required=False,
default=60,
hot_reloadable=True,
title="ACK 兜底超时",
description="手动 ACK 策略下,插件忘记 ACK 时核心自动 ACK 的超时时间(秒)。",
category="可靠性",
),
# 传输引擎配置
ConfigField(
key="transport.stall_timeout_ms",
type="int",
required=False,
default=120000,
hot_reloadable=True,
title="传输停滞超时",
description="传输流判定为停滞的超时时间(毫秒)。",
category="传输引擎",
),
ConfigField(
key="transport.backoff_schedule",
type="str",
required=False,
default="1,2,5,10,30",
hot_reloadable=True,
title="传输退避序列",
description="传输重试的退避时间序列,逗号分隔的秒数列表。",
category="传输引擎",
),
ConfigField(
key="transport.backoff_jitter",
type="float",
required=False,
default=0.2,
hot_reloadable=True,
title="传输退避抖动",
description="传输重试退避的随机抖动系数。",
category="传输引擎",
),
# StreamWorker 断线重连指数退避参数Task 10.3),与 TransportConfig
# DTO 字段一一对应,由 base_worker._loadConfig 通过 ConfigPort 读取。
ConfigField(
key="transport.stream_reconnect_backoff_ms",
type="int",
required=False,
default=1000,
hot_reloadable=True,
title="流重连初始退避",
description="StreamWorker 断线重连的指数退避基数(毫秒),每次重连翻倍直至达到最大退避上限。",
category="传输引擎",
),
ConfigField(
key="transport.stream_reconnect_max_backoff_ms",
type="int",
required=False,
default=30000,
hot_reloadable=True,
title="流重连最大退避",
description="StreamWorker 断线重连指数退避的上限(毫秒),避免退避时间无限增长。",
category="传输引擎",
),
# FR-22 Outbox 重试策略OBX-POLICY-UPDATEJSON 格式:
# {"max_retry": int, "ttl_seconds": int, "retry_backoff_schedule": [int, ...]}
# 由 OutboxHandler.updateRetryPolicy 双写到 ConfigPortRedis
# 支持跨请求 / 跨进程最终一致性。
ConfigField(
key="outbox_retry_policy",
type="json",
required=False,
default=None,
hot_reloadable=True,
title="Outbox 重试策略",
description="出站消息重试策略,包含 max_retry / ttl_seconds / retry_backoff_schedule。",
category="可靠性",
),
# FR-22 Outbox Scanner-Worker 协调Task 10Scanner 判定
# SENT_UNCONFIRMED 无回执超时与 Worker 重试锁 TTL原为硬编码
# 300s/60s现统一收敛至 CONFIG_SCHEMA支持热更新。
ConfigField(
key="outbox_sent_unconfirmed_timeout_seconds",
type="int",
required=False,
default=300,
hot_reloadable=True,
title="Outbox SENT_UNCONFIRMED 超时(秒)",
description="Scanner 判定 SENT_UNCONFIRMED 消息无回执的超时秒数,超过此时间视为可能已发送但无回执",
category="可靠性",
),
ConfigField(
key="outbox_retry_lock_ttl_seconds",
type="int",
required=False,
default=60,
hot_reloadable=True,
title="Outbox 重试锁 TTL",
description="Worker 单条消息重试的分布式锁 TTL覆盖一次网络调用+DB持久化+事件发布的完整周期",
category="可靠性",
),
# === 渠道用户与 Agent 交互链路优化Task 6/7/8===
# 身份解析置信度阈值账户级scope=ACCOUNT低于此阈值的解析结果视为
# 未命中,进入 channel_guest 身份生成路径。缺口补齐:原 _getThreshold
# 使用硬编码默认值,现统一收敛至 CONFIG_SCHEMA。
ConfigField(
key="identity_confidence_threshold",
type="float",
required=False,
default=0.5,
hot_reloadable=True,
scope=ConfigScope.ACCOUNT,
title="身份置信度阈值",
description="身份解析结果低于此阈值时视为未命中,进入 channel_guest 生成路径。",
category="身份与权限",
),
# 新建 Agent 的默认渠道访问级别全局级scope=GLOBAL取值如
# "none" / "read" / "write" 等,由 Agent 创建流程读取。
ConfigField(
key="channel_access_default_level",
type="str",
required=False,
default="none",
hot_reloadable=True,
scope=ConfigScope.GLOBAL,
title="默认渠道访问级别",
description="新建 Agent 默认的渠道访问级别,如 none / read / write。",
category="身份与权限",
),
# 渠道访客身份置信度全局级scope=GLOBALchannel_guest 身份生成时
# 使用此置信度,低于 identity_confidence_threshold 以避免被误判为
# 已解析身份。
ConfigField(
key="channel_guest_confidence",
type="float",
required=False,
default=0.3,
hot_reloadable=True,
scope=ConfigScope.GLOBAL,
title="访客身份置信度",
description="channel_guest 身份生成时使用的置信度,应低于身份置信度阈值。",
category="身份与权限",
),
# 是否启用管理员手动绑定功能全局级scope=GLOBAL控制渠道用户与
# 统一身份的手动绑定入口是否可用。
ConfigField(
key="enable_channel_user_binding",
type="bool",
required=False,
default=True,
hot_reloadable=True,
scope=ConfigScope.GLOBAL,
title="手动绑定开关",
description="是否启用管理员手动绑定渠道用户与统一身份的功能入口。",
category="身份与权限",
),
# === 渠道账号运营流程优化Task 14契约层 CONFIG_SCHEMA 健康检查配置键)===
# 以下 2 个键均为渠道级scope=CHANNEL归属 ConfigPortRedis 热重载,
# CHANNEL 业务策略体系),不得声明在 app_config。
# 凭证失效计数阈值。健康检查探活返回 401/403 时计入凭证失效计数,
# 达阈值后调聚合根 markFailed。窗口外credential_failure_window_seconds
# 重置计数。
ConfigField(
key="credential_failure_threshold",
type="int",
required=False,
default=3,
hot_reloadable=True,
scope=ConfigScope.CHANNEL,
title="凭证失效阈值",
description="健康检查中凭证失效计数的阈值。",
category="健康检查",
),
# 凭证失效计数窗口(秒)。窗口外的失效计数被重置,避免历史故障永久影响账号状态。
ConfigField(
key="credential_failure_window_seconds",
type="int",
required=False,
default=300,
hot_reloadable=True,
scope=ConfigScope.CHANNEL,
title="凭证失效窗口",
description="凭证失效计数的滑动窗口时长(秒)。",
category="健康检查",
),
# 审计日志保留策略全局级scope=GLOBAL。JSON 格式:
# {"retention_days": int, "cleanup_interval_hours": int}
# 控制审计日志的保留天数与清理周期,由审计清理任务读取。
ConfigField(
key="audit_retention_policy",
type="json",
required=False,
default={"retention_days": 90, "cleanup_interval_hours": 24},
hot_reloadable=True,
scope=ConfigScope.GLOBAL,
title="审计保留策略",
description="审计日志保留天数与清理周期配置JSON 格式。",
category="审计与登录",
),
# 扫码登录重定向 URI全局级scope=GLOBAL。扫码登录成功后的前端跳转地址
# 为空时使用默认跳转行为。
ConfigField(
key="qr_login_redirect_uri",
type="str",
required=False,
default="",
hot_reloadable=True,
scope=ConfigScope.GLOBAL,
title="扫码登录跳转地址",
description="扫码登录成功后的前端跳转地址。",
category="审计与登录",
),
# 渠道熔断器配置GLOBAL 作用域),由 ChannelCircuitBreaker 读取。
# 包含连续失败阈值与恢复超时时间;未配置时使用组件内默认值。
ConfigField(
key="circuit_breaker",
type="json",
required=False,
default={"threshold": 5, "recovery_timeout": 60.0},
hot_reloadable=True,
scope=ConfigScope.GLOBAL,
title="渠道熔断器配置",
description="渠道 API 熔断器参数,包含连续失败阈值与恢复超时时间。",
category="可靠性",
),
# === 不可热更新字段hot_reloadable=False===
ConfigField(
key="webhook_port",
type="int",
required=False,
default=None,
hot_reloadable=False,
title="Webhook 端口",
description="渠道 Webhook 服务监听端口。",
category="基础设施",
),
ConfigField(
key="webhook_secret",
type="str",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
title="Webhook 密钥",
description="渠道 Webhook 签名验证密钥,敏感字段。",
category="基础设施",
),
ConfigField(
key="tls_cert",
type="str",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
title="TLS 证书",
description="TLS 证书内容,敏感字段。",
category="基础设施",
),
ConfigField(
key="tls_key",
type="str",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
title="TLS 私钥",
description="TLS 私钥内容,敏感字段。",
category="基础设施",
),
ConfigField(
key="plugin_entry",
type="str",
required=False,
default=None,
hot_reloadable=False,
title="插件入口",
description="插件入口模块路径。",
category="基础设施",
),
ConfigField(
key="database_url",
type="str",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
title="数据库地址",
description="业务数据库连接地址,敏感字段。",
category="基础设施",
),
ConfigField(
key="redis_url",
type="str",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
title="Redis 地址",
description="Redis 连接地址,敏感字段。",
category="基础设施",
),
ConfigField(
key="transport.max_restart_attempts",
type="int",
required=False,
default=None,
hot_reloadable=False,
title="最大重启尝试",
description="传输进程异常后的最大重启次数。",
category="传输引擎",
),
ConfigField(
key="transport.graceful_shutdown_timeout_s",
type="float",
required=False,
default=10.0,
hot_reloadable=False,
title="优雅关闭超时",
description="传输进程优雅关闭的最大等待时间(秒)。",
category="传输引擎",
),
# === 渠道账号运营流程优化Task 3契约层 CONFIG_SCHEMA 凭证配置键)===
# 以下 3 个键均为账户级scope=ACCOUNT归属 ConfigPortRedis 热重载,
# CHANNEL 业务策略/toggles/TTLs 体系),不得声明在 app_config。
# 凭证包,敏感字段加密存储。由 CredentialService 唯一写入入口维护,
# 包含 bot_token/app_secret/user_access_token 等渠道认证凭证。
ConfigField(
key="credentials",
type="dict",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
scope=ConfigScope.ACCOUNT,
title="渠道凭证包",
description="渠道认证凭证集合,敏感字段。",
category="凭证与登录态",
),
# 登录态unlogged/logging_in/logged_in/logout_failed。由 QrLoginService
# 通过 ConfigPort 维护,区分扫码会话状态与最终凭证落库状态。
ConfigField(
key="login_status",
type="str",
required=False,
default="unlogged",
hot_reloadable=False,
scope=ConfigScope.ACCOUNT,
title="登录状态",
description="当前账户扫码登录状态。",
category="凭证与登录态",
),
# 凭证版本号,每次 rotateCredentials 自增。用于乐观锁控制与缓存失效判定。
ConfigField(
key="credential_version",
type="int",
required=False,
default=0,
hot_reloadable=False,
scope=ConfigScope.ACCOUNT,
title="凭证版本",
description="凭证轮换版本号,用于乐观锁与缓存失效。",
category="凭证与登录态",
),
# FR17-P0-3 已应用迁移列表ACCOUNT 作用域target=channel_type 或
# {channel_type}:{account_id})。由 PluginLifecycleManager 在插件 onStart
# 成功后追加 manifest.version 标记配置已加载DoctorService 只读不写。
# hot_reloadable=False非业务策略启动期写入无需热更新。
# default=[] 让首次加载时 ConfigPort.get 返回空列表而非 NotFoundError
# 避免 PluginLifecycleManager 与 DoctorService 的 catch 块接不到异常。
ConfigField(
key="applied_migrations",
type="json",
required=False,
default=[],
hot_reloadable=False,
scope=ConfigScope.ACCOUNT,
title="已应用迁移",
description="账户已应用的插件迁移版本列表。",
category="凭证与登录态",
),
# 渠道命令声明CHANNEL 作用域),由各渠道 CommandAdapter 读取。
# 存储命令列表 JSON未配置时适配器回退为内置默认命令。
ConfigField(
key="commands",
type="json",
required=False,
default=None,
hot_reloadable=True,
scope=ConfigScope.CHANNEL,
title="渠道命令列表",
description="渠道自定义命令声明,未配置时使用内置默认命令。",
category="功能开关",
),
# 安装向导状态CHANNEL 作用域),由 WizardService 读写。
# 存储已应用步骤的配置补丁聚合,支持跨会话恢复向导进度。
ConfigField(
key="wizard_state",
type="json",
required=False,
default={},
hot_reloadable=True,
scope=ConfigScope.CHANNEL,
title="向导状态",
description="安装向导已应用步骤的配置补丁聚合,用于跨会话恢复向导进度。",
category="运营流程",
),
# 安装向导最终配置CHANNEL 作用域),由 WizardService.finalize 写入。
# 存储向导聚合后的完整渠道配置,供账户创建后运行时读取。
ConfigField(
key="channel_config",
type="json",
required=False,
default=None,
hot_reloadable=True,
scope=ConfigScope.CHANNEL,
title="渠道向导配置",
description="安装向导 finalize 后写入的聚合渠道配置,供运行时读取。",
category="运营流程",
),
# 用户级访问令牌ACCOUNT 作用域),由 LoginAdapter 在登出时读取。
# 企业微信 OAuth 无独立 user_access_token此键供 logout 检测登录态存在性。
ConfigField(
key="user_access_token",
type="str",
required=False,
default=None,
hot_reloadable=False,
sensitive=True,
scope=ConfigScope.ACCOUNT,
title="用户访问令牌",
description="用户级 OAuth 访问令牌,用于登录态检测与登出清理。",
category="凭证与登录态",
),
)
# 可热更新的配置项键集合FR-37从 CONFIG_SCHEMA 派生。
HOT_RELOADABLE_KEYS = frozenset(f.key for f in CONFIG_SCHEMA if f.hot_reloadable)
# 不可热更新的配置项键集合FR-37从 CONFIG_SCHEMA 派生。
NON_HOT_RELOADABLE_KEYS = frozenset(f.key for f in CONFIG_SCHEMA if not f.hot_reloadable)
__all__ = ["CONFIG_SCHEMA", "HOT_RELOADABLE_KEYS", "NON_HOT_RELOADABLE_KEYS"]