Files
AstrBot/openspec/agent-message.md
2026-03-27 00:16:43 +08:00

2239 lines
62 KiB
Markdown
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.
# Agent 消息处理流程规范
## 概述
AstrBot Agent 采用**双缓冲区 + 流控**的消息处理模型,实现消息的削峰填谷、限流保护和安全处理。
**核心设计**
- **输入缓冲区**:用户消息暂存,按频率控制消费
- **输出缓冲区**:回复消息暂存,按策略分发
- **流控引擎**:根据 API 限制自动调节消费速率
- **安全层**:防注入、防泄密、防误触
## 架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ Platform Adapter │
│ (QQ / Telegram / Discord / ...) │
└────────────────────────────┬────────────────────────────────────┘
│ commit_event()
┌─────────────────────────────────────────────────────────────────┐
│ Input Message Buffer │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ UserQueue (per user/conversation) │ │
│ │ - metadata: user_id, platform, timestamp, session_id │ │
│ │ - messages: [msg1, msg2, ...] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ FlowControl │
│ (rate limiter) │
└───────────────────────────┼─────────────────────────────────────┘
│ pull_messages()
┌─────────────────────────────────────────────────────────────────┐
│ Agent Core │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Context │───▶│ LLM Loop │───▶│ Tool Call │ │
│ │ Manager │ │ (step loop) │ │ Executor │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
│ produce_result()
┌─────────────────────────────────────────────────────────────────┐
│ Output Buffer │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ResultQueue (per session) │ │
│ │ - content: string / stream │ │
│ │ - format: plain / markdown / html │ │
│ │ - strategy: streaming / segmented / full │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ DispatchStrategy │
│ (streaming / segmented / full) │
└───────────────────────────┼─────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Platform Adapter │
│ (SendResult) │
└─────────────────────────────────────────────────────────────────┘
```
---
## 1. 工具、技能与 Agent 协作体系
### 1.1 三层架构
```
┌─────────────────────────────────────────────────────────────────┐
│ Agent Core (LLM Loop) │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Internal │ │ MCP │ │ Skills │ │
│ │ Tools │ │ Tools │ │ │ │
│ │ (Function │ │ (MCP │ │ (Pre-built │ │
│ │ Tool) │ │ Client) │ │ Agent │ │
│ │ │ │ │ │ Flows) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └───────────────────┴───────────────────┘ │
│ │ │
│ Tool Executor │
└──────────────────────────────┼──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Agent 协作层 │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 本地 │ │ 远程 │ │ 子 Agent │ │
│ │ Subagent │ │ A2A Agent │ │ (MCP/A2A) │ │
│ │ │ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ACP 协议 (Agent 通信) │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 1.2 工具来源
| 来源 | 协议 | 说明 |
|------|------|------|
| **Internal Tools** | 自定义 Python | `FunctionTool`/`ToolSet`Star 插件注册 |
| **MCP Tools** | MCP JSON-RPC 2.0 | 外部 MCP 服务器提供的工具 |
| **Skills** | 自定义协议 | 预构建的 Agent 执行流程模板 |
### 1.3 工具调用决策
```python
class ToolRouter:
"""工具路由"""
def __init__(
self,
internal_toolset: ToolSet,
mcp_clients: dict[str, MCPClient],
skill_executors: dict[str, SkillExecutor],
):
self.internal = internal_toolset
self.mcp = mcp_clients
self.skills = skill_executors
async def route_tool_call(
self,
tool_name: str,
arguments: dict,
context: AgentContext,
) -> ToolResult:
"""路由工具调用"""
# 1. 检查内部工具
internal_tool = self.internal.get_tool(tool_name)
if internal_tool:
return await self._call_internal(internal_tool, arguments, context)
# 2. 检查 MCP 工具
for client_name, client in self.mcp.items():
if client.has_tool(tool_name):
return await client.call_tool(tool_name, arguments)
# 3. 检查 Skills
skill = self.skills.get(tool_name)
if skill:
return await self._execute_skill(skill, arguments, context)
raise ToolNotFoundError(f"Tool not found: {tool_name}")
```
### 1.4 Agent 协作ACP 协议)
```python
class ACPAgentClient:
"""ACP Agent 客户端"""
async def call_agent(
self,
agent_name: str,
action: str,
args: dict,
stream: bool = True,
) -> AsyncIterator[AgentEvent] | AgentResult:
"""调用远程 Agent"""
request = ACPRequest(
method="agent/call",
params={
"agent": agent_name,
"action": action,
"args": args,
}
)
if stream:
return self._stream_request(request)
else:
return await self._send_request(request)
async def list_agents(self) -> list[AgentCard]:
"""列出可用 Agent"""
response = await self._send_request(
ACPRequest(method="agent/list")
)
return [AgentCard(**a) for a in response.result["agents"]]
```
### 1.5 Skills 执行
```python
class SkillExecutor:
"""Skill 执行器"""
def __init__(self, skill_registry: SkillRegistry):
self.registry = skill_registry
async def execute(
self,
skill_name: str,
input_data: dict,
context: AgentContext,
) -> SkillResult:
"""执行 Skill"""
skill = self.registry.get(skill_name)
if not skill:
raise SkillNotFoundError(f"Skill not found: {skill_name}")
# Skill 可以包含多个步骤
steps = skill.get_steps()
results = []
for step in steps:
# 每个步骤可以是工具调用或 Agent 调用
if step.type == "tool":
result = await self._call_tool(step.tool, step.args)
elif step.type == "agent":
result = await self._call_agent(step.agent, step.action, step.args)
elif step.type == "llm":
result = await self._call_llm(step.prompt, context)
results.append(result)
# 检查是否需要停止
if step.on_result == "stop_if_success" and result.success:
break
return SkillResult(
skill_name=skill_name,
steps=results,
final_output=results[-1] if results else None,
)
```
### 1.6 配置
```yaml
# agent.yaml
# 工具配置
tools:
# 内部工具
internal:
enabled: true
max_per_request: 128
# MCP 工具
mcp:
enabled: true
servers: [] # MCP 服务器配置
# Skills
skills:
enabled: true
registry_path: "$XDG_DATA_HOME/astrbot/skills/"
# Agent 协作配置
agent_collaboration:
# ACP 配置
acp:
enabled: true
endpoints:
- name: "local"
type: "unix"
path: "/run/astrbot/acp.sock"
# 子 Agent 配置
subagents:
enabled: true
max_parallel: 3
timeout: 300
# Agent 发现
discovery:
# 自动发现同进程内的 Subagent
auto_discover_internal: true
# 定期刷新远程 Agent 列表
refresh_interval: 60
```
---
## 2. 输入缓冲区Input Buffer
### 2.1 队列结构
```rust
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InputMessage {
/// 全局唯一 ID
pub message_id: String,
/// 平台标识
pub platform: String,
/// 用户 ID
pub user_id: String,
/// 会话 ID
pub conversation_id: String,
/// 消息内容
pub content: MessageContent,
/// 到达时间
pub timestamp: f64,
/// 扩展元数据
pub metadata: HashMap<String, String>,
/// 优先级(越高越先处理)
#[serde(default)]
pub priority: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MessageContent {
Plain(String),
Chain(Vec<MessageSegment>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageSegment {
pub segment_type: String,
pub content: String,
#[serde(default)]
pub metadata: HashMap<String, String>,
}
pub struct UserMessageQueue {
pub user_id: String,
pub session_id: String,
messages: VecDeque<InputMessage>,
metadata: HashMap<String, String>,
pub created_at: f64,
pub updated_at: f64,
pub max_size: usize,
pub max_age: f64,
}
impl UserMessageQueue {
pub fn new(user_id: String, session_id: String) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64();
Self {
user_id,
session_id,
messages: VecDeque::new(),
metadata: HashMap::new(),
created_at: now,
updated_at: now,
max_size: 1000,
max_age: 3600.0,
}
}
pub fn push(&mut self, msg: InputMessage) {
self.messages.push_back(msg);
self.updated_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs_f64();
}
pub fn pop(&mut self) -> Option<InputMessage> {
self.messages.pop_front()
}
pub fn len(&self) -> usize {
self.messages.len()
}
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
}
```
### 2.2 缓冲区配置
```yaml
# agent.yaml
input_buffer:
# 单用户队列最大消息数
max_queue_size: 1000
# 消息最大存活时间(秒)
max_message_age: 3600
# 超出限制时的处理策略
overflow_strategy: "drop_oldest" # drop_oldest | drop_newest | block
# 丢弃消息时的提示前缀
overflow_hint: "[消息过多,部分早期消息已丢弃]"
# 是否按用户隔离队列
per_user_queue: true
# 是否按会话隔离队列
per_conversation_queue: true
```
### 2.3 溢出保护策略
| 策略 | 说明 | 适用场景 |
|------|------|----------|
| `drop_oldest` | 丢弃最旧的消息,保留最新的 | 高频聊天,侧重时效性 |
| `drop_newest` | 丢弃最新的消息,保留旧的 | 重要指令,不容丢失 |
| `block` | 阻塞输入,直到队列有空位 | 重要对话,不容任何丢弃 |
**溢出时的处理**
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OverflowStrategy {
DropOldest,
DropNewest,
Block,
}
pub struct InputBuffer {
queues: HashMap<String, Arc<tokio::sync::Mutex<UserMessageQueue>>>,
overflow_strategy: OverflowStrategy,
overflow_hint: String,
}
impl InputBuffer {
/// 添加消息到队列
pub async fn add_message(&self, queue_id: &str, message: InputMessage) -> Result<(), BufferError> {
let queue = self.queues.get(queue_id)
.ok_or(BufferError::QueueNotFound)?;
let mut queue = queue.lock().await;
if queue.messages.len() >= queue.max_size {
match self.overflow_strategy {
OverflowStrategy::DropOldest => {
if let Some(old_msg) = queue.messages.pop_front() {
// 在丢弃的消息前插入提示
let hint = InputMessage {
message_id: "system_hint".into(),
content: MessageContent::Plain(format!(
"[{} 丢弃于 {}]",
self.overflow_hint,
old_msg.timestamp
)),
..message.clone()
};
queue.messages.push_front(hint);
}
queue.messages.push_back(message);
}
OverflowStrategy::DropNewest => {
// 丢弃新消息,不插入
}
OverflowStrategy::Block => {
// 等待直到队列有空位
while queue.messages.len() >= queue.max_size {
let queue_clone = queue.clone();
drop(queue);
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
queue = queue_clone.lock().await;
}
queue.messages.push_back(message);
}
}
} else {
queue.messages.push_back(message);
}
Ok(())
}
}
```
---
## 3. 流控引擎Flow Control
### 3.1 速率限制配置
```yaml
# agent.yaml
flow_control:
# 消费速率模式
mode: "auto" # auto | manual
# 手动模式:每秒处理消息数
manual_rate: 10
# 自动模式:基于 LLM API 限制计算
auto:
# LLM API 每分钟请求限制
api_rpm_limit: 60
# 每次请求预计处理消息数
messages_per_request: 5
# 安全系数(留一定余量)
safety_margin: 0.8
# 最小消费间隔(秒)
min_interval: 0.5
# 最大消费间隔(秒)
max_interval: 10
```
### 3.2 速率计算公式
```
effective_rate = min(api_rpm_limit * messages_per_request * safety_margin, 1/min_interval)
consume_interval = 1 / effective_rate
```
**示例**
- API RPM = 60
- 每请求处理 5 条消息
- 安全系数 = 0.8
- 有效速率 = 60 * 5 * 0.8 = 240 消息/分钟 = 4 消息/秒
- 消费间隔 = 0.25 秒
### 3.3 令牌桶实现
```python
class TokenBucket:
"""令牌桶流控"""
def __init__(
self,
rate: float, # 每秒令牌数
capacity: float, # 桶容量
burst: float = None, # 突发容量
):
self.rate = rate
self.capacity = capacity
self.burst = burst or capacity
self.tokens = capacity
self.last_update = time.monotonic()
async def acquire(self, tokens: float = 1.0) -> float:
"""获取令牌,返回需要等待的秒数"""
now = time.monotonic()
elapsed = now - self.last_update
self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
self.last_update = now
if self.tokens >= tokens:
self.tokens -= tokens
return 0.0
wait_time = (tokens - self.tokens) / self.rate
return wait_time
async def wait_and_acquire(self, tokens: float = 1.0) -> None:
"""等待直到获取令牌"""
wait = await self.acquire(tokens)
if wait > 0:
await asyncio.sleep(wait)
```
### 3.4 优先级调度
```python
class PriorityScheduler:
"""优先级调度器"""
def __init__(self, buckets: dict[str, TokenBucket]):
self.buckets = buckets # per-user or per-session
async def next_message(self) -> InputMessage | None:
"""获取下一条待处理消息(按优先级)"""
# 1. 收集所有非空队列
candidates = []
for user_id, queue in self.queues.items():
if not queue.messages:
continue
# 2. 计算该用户的可用速率
bucket = self.buckets.get(user_id)
if not bucket:
continue
# 3. 获取队首消息peek不移除
msg = queue.messages[0]
candidates.append((msg, bucket, user_id))
if not candidates:
return None
# 4. 按优先级 + 可用性排序
# 优先级相同时,优先处理令牌充足的
candidates.sort(
key=lambda x: (
-x[0].priority,
x[1].tokens / x[1].rate if x[1].rate > 0 else 0
)
)
# 5. 等待最紧急消息的令牌
msg, bucket, user_id = candidates[0]
await bucket.wait_and_acquire(1.0)
# 6. 移除并返回
return queue.messages.popleft()
```
---
## 4. Agent 核心Agent Core
### 4.1 上下文管理Context Manager
```python
@dataclass
class AgentContext:
"""Agent 执行上下文"""
messages: list[Message] # 消息历史
system_prompt: str # 系统提示
tools: list[ToolDefinition] # 可用工具
memory: MemoryBank # 记忆存储
metadata: dict # 扩展元数据
class ContextManager:
"""上下文管理器"""
def __init__(self, config: ContextConfig):
self.max_tokens: int = config.max_context_tokens
self.compress_threshold: float = config.compress_threshold
self.keep_recent: int = config.keep_recent_messages
def build_context(
self,
queue: UserMessageQueue,
memory: MemoryBank,
) -> AgentContext:
"""构建 Agent 执行上下文"""
# 1. 从队列获取消息
raw_messages = list(queue.messages)
# 2. 应用安全过滤
raw_messages = self.apply_security_filters(raw_messages)
# 3. 构建消息列表
messages = self.build_message_list(raw_messages)
# 4. 检查是否需要压缩
total_tokens = self.estimate_tokens(messages)
if total_tokens > self.max_tokens * self.compress_threshold:
messages = self.compress_context(messages, memory)
# 5. 添加系统提示
system_prompt = self.build_system_prompt()
return AgentContext(
messages=messages,
system_prompt=system_prompt,
tools=self.get_available_tools(),
memory=memory,
)
def compress_context(
self,
messages: list[Message],
memory: MemoryBank,
) -> list[Message]:
"""压缩上下文"""
# 保留最近 N 条消息
recent = messages[-self.keep_recent:]
# 提取历史消息进行压缩
history = messages[:-self.keep_recent]
# 摘要历史消息并存入记忆
if history:
summary = self.summarize(history)
memory.add(Message(
role="system",
content=f"[历史摘要] {summary}",
metadata={"type": "summary"}
))
return recent
```
### 4.2 上下文配置
```yaml
# agent.yaml
context:
# 最大上下文 token 数
max_context_tokens: 128000
# 触发压缩的阈值(比例)
compress_threshold: 0.85
# 压缩后保留的最近消息数
keep_recent_messages: 6
# 压缩提供者(为空则使用主 Provider
compress_provider_id: ""
# 压缩提示词
compress_instruction: |
请简洁地总结对话要点,保留关键信息如:
- 用户的主要需求或问题
- 已确定的方案或结论
- 未完成的任务
# 消息保留策略
retention:
# 保留最近 N 小时内的原始消息
recent_hours: 24
# 超出后转为摘要存储
summarize_after: true
```
---
## 5. 工具调用策略Tool Calling Strategy
### 4.1 工具调用最佳实践
```yaml
# agent.yaml
tool_calling:
# 工具调用策略
strategy: "smart" # eager | sequential | smart
# 每次请求最大工具调用数
max_calls_per_request: 128
# 工具调用超时(秒)
timeout: 60
# 工具调用失败重试次数
max_retries: 3
# 是否并行调用独立工具
parallel_calls: true
# 并行调用最大数量
max_parallel_calls: 5
# 工具结果的最大 token 数(截断)
max_result_tokens: 4096
# 是否在工具调用后立即返回中间结果
stream_intermediate: true
```
### 4.2 工具调用流程
```python
class ToolCallingPolicy:
"""工具调用策略"""
async def execute_tools(
self,
llm_response: LLMResponse,
context: AgentContext,
) -> list[ToolResult]:
"""执行工具调用"""
# 1. 解析工具调用请求
tool_calls = llm_response.tool_calls or []
if not tool_calls:
return []
# 2. 按策略分组
groups = self._group_by_dependency(tool_calls)
results = []
# 3. 按组执行
for group in groups:
if self._can_parallel(group):
# 并行执行
group_results = await asyncio.gather(
*[self._execute_single(call, context) for call in group],
return_exceptions=True
)
else:
# 串行执行
group_results = []
for call in group:
result = await self._execute_single(call, context)
group_results.append(result)
results.extend(group_results)
# 4. 检查是否超过限制
if len(results) >= self.config.max_calls_per_request:
break
# 5. 如果需要流式中间结果
if self.config.stream_intermediate:
yield ToolCallingEvent(
type="intermediate",
results=group_results
)
return results
def _group_by_dependency(
self,
tool_calls: list[ToolCall],
) -> list[list[ToolCall]]:
"""按依赖关系分组"""
groups = []
current_group = []
for call in tool_calls:
# 检查是否依赖前一个工具的结果
if call.arguments_depends_on_previous and current_group:
# 依赖:将当前调用加入前一个组
current_group.append(call)
else:
# 不依赖:开启新组
if current_group:
groups.append(current_group)
current_group = [call]
if current_group:
groups.append(current_group)
return groups
```
### 4.3 工具选择策略
```python
class ToolSelector:
"""工具选择策略"""
def __init__(self, config: ToolSelectionConfig):
self.max_tools_per_request = config.max_tools_per_request
self.prefer_recent = config.prefer_recent_tools
def select_tools(
self,
available_tools: list[Tool],
query: str,
context: AgentContext,
) -> list[Tool]:
"""选择最相关的工具"""
# 1. 计算工具与查询的相关性
scored = []
for tool in available_tools:
score = self._calculate_relevance(tool, query, context)
scored.append((score, tool))
# 2. 排序并截取
scored.sort(key=lambda x: -x[0])
selected = scored[:self.max_tools_per_request]
# 3. 如果启用了最近使用优先
if self.prefer_recent:
selected = self._boost_recent(selected, context)
return [t for _, t in selected]
def _calculate_relevance(
self,
tool: Tool,
query: str,
context: AgentContext,
) -> float:
"""计算相关性分数"""
base_score = 0.0
# 工具名称匹配
if any(word in tool.name.lower() for word in query.lower().split()):
base_score += 0.3
# 工具描述匹配
if tool.description:
# 简单的词重叠计算
query_words = set(query.lower().split())
desc_words = set(tool.description.lower().split())
overlap = len(query_words & desc_words)
base_score += overlap * 0.1
# 最近使用过的工具加权
if context.metadata.get("recent_tools"):
if tool.name in context.metadata["recent_tools"]:
base_score += 0.2
return base_score
```
---
## 6. 安全层Security Layer
### 6.1 安全配置
```yaml
# agent.yaml
security:
# 防注入配置
injection:
# 启用防注入
enable: true
# 检测模式
mode: "strict" # strict | moderate | permissive
# 注入模式识别
patterns:
- name: "role_play_injection"
regex: "(?i)(you are now|forget previous|ignore all)"
severity: "high"
- name: "system_prompt_leak"
regex: "(?i)(repeat your? (system|initial) (prompt|instructions))"
severity: "high"
- name: "code_injection"
regex: "(?i)(```(system|prompt|instructor))"
severity: "medium"
# 触发时的处理策略
on_detect: "sanitize" # sanitize | block | warn
# 是否记录检测日志
log_detections: true
# 内容过滤配置
content_filter:
# 启用内容过滤
enable: true
# 过滤级别
level: "standard" # strict | standard | minimal
# 敏感词列表(文件路径或内联)
blocklist: []
# 替换字符
replacement: "[已过滤]"
# 泄密防护
leakage_prevention:
# 阻止 Agent 读取敏感文件模式
blocked_file_patterns:
- "**/.env"
- "**/secrets.yaml"
- "**/*password*"
- "**/.git/credentials"
# 阻止 Agent 输出敏感信息模式
blocked_output_patterns:
- "(?i)api[_-]?key"
- "(?i)secret"
- "(?i)password"
# 替换为占位符
placeholder: "[REDACTED]"
```
### 6.2 安全过滤器实现
```python
class SecurityFilter:
"""安全过滤器"""
def __init__(self, config: SecurityConfig):
self.config = config
self.compiled_patterns = [
(p["name"], re.compile(p["regex"]), p["severity"])
for p in config.injection.patterns
]
def filter_messages(
self,
messages: list[InputMessage],
) -> list[InputMessage]:
"""过滤输入消息"""
filtered = []
for msg in messages:
# 1. 内容过滤
if self.config.content_filter.enable:
msg.content = self._filter_content(msg.content)
# 2. 注入检测
if self.config.injection.enable:
detections = self._detect_injection(msg.content)
if detections:
action = self._handle_injection(detections, msg)
if action == "skip":
continue
filtered.append(msg)
return filtered
def filter_output(
self,
content: str,
context: AgentContext,
) -> str:
"""过滤输出内容"""
# 1. 泄密防护 - 移除敏感信息
if self.config.leakage_prevention:
content = self._redact_sensitive(content)
return content
def _detect_injection(self, content: str) -> list[Detection]:
"""检测注入攻击"""
detections = []
for name, pattern, severity in self.compiled_patterns:
if pattern.search(content):
detections.append(Detection(
name=name,
severity=severity,
matched=pattern.findall(content),
))
return detections
def _handle_injection(
self,
detections: list[Detection],
message: InputMessage,
) -> str:
"""处理注入检测"""
high_severity = any(d.severity == "high" for d in detections)
if high_severity and self.config.injection.on_detect == "block":
# 记录并阻止
logging.warning(f"Blocked injection: {detections}")
return "skip"
elif self.config.injection.on_detect == "sanitize":
# 消毒处理
for detection in detections:
message.content = message.content.replace(
detection.matched,
self.config.content_filter.replacement,
)
return "sanitize"
return "allow"
def _filter_content(self, content: str) -> str:
"""内容过滤"""
if not self.config.content_filter.enable:
return content
for pattern in self.config.content_filter.blocklist:
content = re.sub(pattern, self.config.content_filter.replacement, content)
return content
```
---
## 7. 权限模型Permission Model
### 7.1 设计原则
遵循 **Unix 哲学**,权限模型采用类似 `rwx` 的能力Capability设计
| 原则 | 说明 |
|------|------|
| **最小权限** | 只授予完成任务所需的最小权限集 |
| **能力继承** | 高权限自动包含低权限的能力 |
| **可组合** | 权限可以灵活组合,适应不同场景 |
| **可委托** | 支持权限的委托和回收 |
### 7.2 角色定义
```rust
/// 角色枚举,类比 Unix 用户组
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Role {
Owner = 0o700, // 超级管理员/拥有者
Admin = 0o600, // 普通管理员
Member = 0o400, // 普通成员
Guest = 0o100, // 访客(受限)
Blocked = 0o000, // 被封禁
}
bitflags::bitflags! {
/// 权限枚举,类比 rwx
pub struct Permission: u16 {
// 基础权限
const READ = 0o400; // 读取权限
const WRITE = 0o200; // 写入权限
const EXECUTE = 0o100; // 执行权限
// 消息权限
const SEND_MESSAGE = 0o040; // 发送消息
const SEND_MEDIA = 0o020; // 发送媒体
const SEND_COMMAND = 0o010; // 发送命令
// 管理权限
const MANAGE_MEMBER = 0o004; // 管理成员
const MANAGE_CONFIG = 0o002; // 管理配置
const MANAGE_PERMISSION = 0o001; // 管理权限
// 特殊权限
const BOT_ADMIN = 0o700; // Bot 管理员(全权限)
const OWNER_ONLY = 0o100; // 仅拥有者可用
}
}
impl Role {
/// 检查角色是否拥有指定权限
pub fn has_permission(&self, permission: Permission) -> bool {
let role_bits = self.bits();
(role_bits & permission.bits()) == permission.bits()
}
/// 获取角色的权限位
fn bits(&self) -> u16 {
*self as u16
}
}
```
### 7.3 能力矩阵
```
┌──────────────────┬───────┬───────┬────────┬────────┬──────────┐
│ 能力 │ OWNER │ ADMIN │ MEMBER │ GUEST │ BLOCKED │
├──────────────────┼───────┼───────┼────────┼────────┼──────────┤
│ 读取消息 │ ✓ │ ✓ │ ✓ │ ✓ │ ✗ │
│ 发送普通消息 │ ✓ │ ✓ │ ✓ │ ✓ │ ✗ │
│ 发送媒体 │ ✓ │ ✓ │ ✓ │ ✗ │ ✗ │
│ 发送斜杠命令 │ ✓ │ ✓ │ ✓ │ ✗ │ ✗ │
│ 使用管理员命令 │ ✓ │ ✓ │ ✗ │ ✗ │ ✗ │
│ 管理成员 │ ✓ │ ✓ │ ✗ │ ✗ │ ✗ │
│ 修改配置 │ ✓ │ ✗ │ ✗ │ ✗ │ ✗ │
│ 转让所有权 │ ✓ │ ✗ │ ✗ │ ✗ │ ✗ │
│ 踢出 Bot │ ✓ │ ✗ │ ✗ │ ✗ │ ✗ │
└──────────────────┴───────┴───────┴────────┴────────┴──────────┘
```
### 7.4 权限检查流程
```rust
use async_trait::async_trait;
#[async_trait]
pub trait PermissionCheck {
async fn check_message(
&self,
event: &InputMessage,
context: &AgentContext,
) -> PermissionResult;
}
pub struct PermissionMiddleware {
role_config: RoleConfig,
command_permissions: HashMap<String, Permission>,
}
#[derive(Debug)]
pub struct PermissionResult {
pub allowed: bool,
pub reason: Option<String>,
}
impl PermissionMiddleware {
/// 检查消息权限
async fn check_message(
&self,
event: &InputMessage,
context: &AgentContext,
) -> PermissionResult {
// 1. 获取发送者角色
let role = self
.get_user_role(&event.user_id, &event.conversation_id)
.await;
// 2. 检查基础消息权限
if !role.has_permission(Permission::SEND_MESSAGE) {
return PermissionResult {
allowed: false,
reason: Some("用户被禁止发送消息".into()),
};
}
// 3. 检查媒体权限
if event.has_media && !role.has_permission(Permission::SEND_MEDIA) {
return PermissionResult {
allowed: false,
reason: Some("用户被禁止发送媒体".into()),
};
}
// 4. 检查命令权限
if event.is_command {
let cmd_perm = self
.command_permissions
.get(&event.command_name)
.copied()
.unwrap_or(Permission::EXECUTE);
if !role.has_permission(cmd_perm) {
return PermissionResult {
allowed: false,
reason: Some(format!("用户无权执行命令: {}", event.command_name)),
};
}
}
PermissionResult { allowed: true, reason: None }
}
}
```
### 7.5 命令权限配置
```yaml
# agent.yaml
permissions:
# 默认角色权限
default_role: "member"
# 角色能力定义
roles:
owner:
capabilities: 0o700
inherits: ["admin"]
admin:
capabilities: 0o600
inherits: ["member"]
member:
capabilities: 0o400
inherits: ["guest"]
guest:
capabilities: 0o100
inherits: []
blocked:
capabilities: 0o000
inherits: []
# 斜杠命令权限
commands:
# 公开命令(所有人均可使用)
public:
- "/help"
- "/status"
- "/ping"
# 成员命令member 及以上)
member:
- "/search"
- "/weather"
- "/translate"
# 管理员命令admin 及以上)
admin:
- "/kick"
- "/ban"
- "/mute"
- "/warn"
- "/config"
# 拥有者命令(仅 owner
owner:
- "/transfer"
- "/delete"
- "/backup"
- "/reload"
# 权限继承配置
inheritance:
enabled: true
max_depth: 5 # 最大继承深度,防止循环
```
### 7.6 用户角色管理
```rust
#[async_trait]
pub trait RoleManager: Send + Sync {
/// 获取用户在特定会话中的角色
async fn get_role(
&self,
user_id: &str,
conversation_id: &str,
) -> Role;
/// 设置用户角色(需要相应权限)
async fn set_role(
&self,
user_id: &str,
conversation_id: &str,
role: Role,
operator_id: &str,
) -> Result<(), PermissionDenied>;
/// 转让所有权
async fn transfer_ownership(
&self,
conversation_id: &str,
new_owner_id: &str,
) -> Result<(), PermissionDenied>;
}
pub struct SqliteRoleManager {
pool: SqlitePool,
}
#[derive(Debug, thiserror::Error)]
pub enum PermissionDenied {
#[error("权限不足: {0}")]
Insufficient(String),
#[error("无法设置比自己更高的权限")]
CannotElevate,
}
#[async_trait]
impl RoleManager for SqliteRoleManager {
async fn get_role(
&self,
user_id: &str,
conversation_id: &str,
) -> Role {
// 1. 检查全局管理员
if self.is_global_admin(user_id).await {
return Role::Owner;
}
// 2. 检查会话特定角色
if let Some(role_data) = self.storage.get_user_role(user_id, conversation_id).await {
return Role::from_bits(role_data.role);
}
// 3. 返回默认角色
Role::Member
}
async fn set_role(
&self,
user_id: &str,
conversation_id: &str,
role: Role,
operator_id: &str,
) -> Result<(), PermissionDenied> {
let operator_role = self.get_role(operator_id, conversation_id).await;
// 检查操作者权限
if role.bits() > operator_role.bits() {
return Err(PermissionDenied::CannotElevate);
}
self.storage
.set_user_role(user_id, conversation_id, role.bits())
.await;
Ok(())
}
}
```
### 7.7 会话级权限配置
```rust
#[derive(Debug, Clone)]
pub struct ConversationPermissions {
pub conversation_id: String,
// 基础权限
pub default_role: Role,
pub allow_guest_read: bool,
pub allow_guest_send: bool,
// 功能开关
pub allow_media: bool,
pub allow_commands: bool,
pub allow_ai_responses: bool,
// 限制
pub max_message_length: usize,
pub max_messages_per_minute: usize,
pub max_commands_per_minute: usize,
// 白名单/黑名单
pub whitelist: Vec<String>,
pub blacklist: Vec<String>,
}
impl ConversationPermissions {
/// 检查用户是否允许执行操作
pub fn check_user_allowed(&self, user_id: &str, permission: Permission) -> bool {
if self.blacklist.contains(&user_id.to_string()) {
return false;
}
if !self.whitelist.is_empty() && !self.whitelist.contains(&user_id.to_string()) {
return false;
}
true
}
}
```
### 7.8 权限事件
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionEvent {
RoleChanged,
PermissionDenied,
UserBanned,
UserUnbanned,
CommandBlocked,
OwnershipTransferred,
}
#[derive(Debug, Clone)]
pub struct PermissionAuditLog {
pub event: PermissionEvent,
pub operator_id: String,
pub target_id: String,
pub conversation_id: String,
pub details: HashMap<String, String>,
pub timestamp: i64,
}
```
### 7.9 与 Unix 的类比
```
┌─────────────────┬────────────────────────┐
│ Unix 概念 │ AstrBot 对应 │
├─────────────────┼────────────────────────┤
│ 用户 (User) │ 用户 (User) │
│ 用户组 (Group) │ 会话 (Conversation) │
│ root 用户 │ Owner (拥有者) │
│ sudo 用户 │ Admin (管理员) │
│ 普通用户 │ Member (成员) │
│ 访客 │ Guest (访客) │
│ 文件权限 rwx │ 能力 (Capability) │
│ chmod │ set_role │
│ chown │ transfer_ownership │
│ /etc/passwd │ Role Storage │
└─────────────────┴────────────────────────┘
```
---
## 8. 输出缓冲区Output Buffer
### 8.1 队列结构
```python
@dataclass
class OutputMessage:
"""输出消息单元"""
session_id: str
content: str | AsyncIterator[str] # 支持流式
format: str = "plain" # plain | markdown | html
strategy: OutputStrategy = OutputStrategy.FULL
metadata: dict = field(default_factory=dict)
# 流式相关
stream_start_time: float | None = None
total_sent: int = 0
@dataclass
class ResultQueue:
"""结果队列"""
session_id: str
results: deque[OutputMessage]
max_size: int = 100
allow_streaming: bool = True
class OutputStrategy(Enum):
"""输出策略"""
STREAMING = "streaming" # 流式输出
SEGMENTED = "segmented" # 智能分段
FULL = "full" # 一次性输出
```
### 8.2 输出策略
```yaml
# agent.yaml
output:
# 默认输出策略
default_strategy: "streaming" # streaming | segmented | full
# 流式配置
streaming:
# 启用流式
enable: true
# 流式 Chunk 大小(字符数)
chunk_size: 20
# Chunk 之间的间隔(秒)
chunk_interval: 0.05
# 智能分段配置
segmented:
# 启用智能分段
enable: true
# 触发分段的字数阈值
threshold: 500
# 分段方式
mode: "sentence" # sentence | word_count | regex
# 按句子分段时的最小长度
min_segment_length: 50
# 分段正则(当 mode=regex
split_regex: "[。!?;\n]+"
# 段落之间的随机间隔(秒)
random_interval: "0.5,2.0"
# 是否在分段前添加省略号
add_ellipsis: true
# 平台适配
platform_adaptation:
# 平台与策略映射
strategy_by_platform:
telegram: "segmented" # Telegram 有字数限制
discord: "segmented" # Discord 也有限制
qq: "segmented"
webchat: "streaming" # WebChat 支持流式
# 平台消息长度限制
max_length_by_platform:
telegram: 4096
discord: 2000
qq: 500
# 输出缓冲配置
buffer:
# 最大缓冲消息数
max_size: 100
# 消息最大存活时间(秒)
max_age: 300
# 溢出策略
overflow_strategy: "drop_oldest"
```
### 8.3 分段器实现
```python
class SmartSegmenter:
"""智能分段器"""
def __init__(self, config: SegmentedConfig):
self.config = config
def segment(self, content: str) -> list[str]:
"""将内容分段"""
if len(content) < self.config.threshold:
return [content]
if self.config.mode == "sentence":
return self._split_by_sentence(content)
elif self.config.mode == "word_count":
return self._split_by_word_count(content)
elif self.config.mode == "regex":
return self._split_by_regex(content)
return [content]
def _split_by_sentence(self, content: str) -> list[str]:
"""按句子分段"""
sentences = re.split(
self.config.split_regex,
content,
)
segments = []
current = []
for sentence in sentences:
if not sentence.strip():
continue
current.append(sentence)
current_text = "".join(current)
# 如果当前段落达到阈值
if len(current_text) >= self.config.threshold:
segment = "".join(current)
if self.config.add_ellipsis and len(segments) > 0:
segment = "..." + segment.lstrip()
segments.append(segment)
current = []
# 处理剩余内容
if current:
remaining = "".join(current)
if remaining.strip():
if self.config.add_ellipsis and segments:
remaining = "..." + remaining.lstrip()
segments.append(remaining)
return segments
async def stream_segments(
self,
content: str,
output: OutputMessage,
sender: Callable[[str], Awaitable[None]],
) -> None:
"""流式发送分段"""
segments = self.segment(content)
for i, segment in enumerate(segments):
# 发送当前分段
await sender(segment)
# 添加间隔(随机)
if i < len(segments) - 1:
interval = self._random_interval()
await asyncio.sleep(interval)
def _random_interval(self) -> float:
"""生成随机间隔"""
import random
parts = self.config.random_interval.split(",")
return random.uniform(float(parts[0]), float(parts[1]))
```
### 8.4 流式输出器
```python
class StreamingOutput:
"""流式输出器"""
def __init__(self, config: StreamingConfig):
self.config = config
async def stream(
self,
content: str,
sender: Callable[[str], Awaitable[None]],
) -> None:
"""流式输出内容"""
start = 0
while start < len(content):
end = start + self.config.chunk_size
chunk = content[start:end]
await sender(chunk)
start = end
# 添加短暂间隔
if start < len(content):
await asyncio.sleep(self.config.chunk_interval)
def create_stream(
self,
content: str,
) -> AsyncIterator[str]:
"""创建流式迭代器"""
async def generator():
start = 0
while start < len(content):
end = start + self.config.chunk_size
chunk = content[start:end]
yield chunk
start = end
if start < len(content):
await asyncio.sleep(self.config.chunk_interval)
return generator()
```
---
## 9. 记忆管理Memory Management
### 9.1 记忆存储配置
```yaml
# agent.yaml
memory:
# 记忆存储类型
backend: "sqlite" # sqlite | redis | memory
# SQLite 配置
sqlite:
path: "$XDG_DATA_HOME/astrbot/state/memory.db"
# Redis 配置
redis:
host: "localhost"
port: 6379
db: 0
prefix: "astrbot:memory:"
# 记忆保留策略
retention:
# 工作记忆:保留在数据库中的时间(天)
working_memory_days: 7
# 长期记忆:超过后转为归档
long_term_threshold_days: 30
# 自动摘要阈值(对话轮数)
auto_summary_threshold: 50
# 每次摘要保留的关键信息数
summary_keep_key_points: 5
# 上下文窗口内的记忆
context_window:
# 保留最近 N 轮对话的完整记忆
recent_rounds: 10
# 超出后转为摘要
summarize_beyond: true
```
### 9.2 记忆类型
```python
class MemoryType(Enum):
"""记忆类型"""
WORKING = "working" # 工作记忆(当前会话)
EPISODIC = "episodic" # 情景记忆(历史事件)
SEMANTIC = "semantic" # 语义记忆(持久知识)
@dataclass
class MemoryEntry:
"""记忆条目"""
id: str
type: MemoryType
content: str
embedding: list[float] | None = None
metadata: dict = field(default_factory=dict)
created_at: float
updated_at: float
access_count: int = 0
importance: float = 0.5 # 0-1 重要性评分
class MemoryBank:
"""记忆库"""
def __init__(self, config: MemoryConfig):
self.config = config
self.backend = self._create_backend(config)
self._cache: dict[str, MemoryEntry] = {}
self._cache_max_size = 100
async def add(self, message: Message) -> None:
"""添加记忆"""
entry = MemoryEntry(
id=str(uuid.uuid4()),
type=MemoryType.EPISODIC,
content=message.content,
metadata={
"role": message.role,
"user_id": message.metadata.get("user_id"),
"session_id": message.metadata.get("session_id"),
},
created_at=time.time(),
updated_at=time.time(),
)
await self.backend.save(entry)
async def search(
self,
query: str,
limit: int = 5,
memory_types: list[MemoryType] | None = None,
) -> list[MemoryEntry]:
"""搜索记忆"""
# 1. 如果有缓存,直接返回
cache_key = f"{query}:{limit}"
if cache_key in self._cache:
return self._cache[cache_key]
# 2. 向量搜索
results = await self.backend.search(
query=query,
limit=limit,
memory_types=memory_types,
)
# 3. 更新访问计数
for entry in results:
entry.access_count += 1
await self.backend.update(entry)
# 4. 缓存
if len(self._cache) >= self._cache_max_size:
# LRU 淘汰
oldest = min(self._cache.values(), key=lambda x: x.access_count)
del self._cache[oldest.id]
self._cache[cache_key] = results
return results
async def summarize_old(
self,
before_timestamp: float,
) -> str:
"""摘要旧记忆"""
# 1. 获取指定时间前的记忆
entries = await self.backend.get_before(before_timestamp)
if not entries:
return ""
# 2. 构建摘要
summary_prompt = f"""请简洁总结以下对话要点:
{chr(10).join(f"- {e.content}" for e in entries)}
保留关键信息:
- 主要话题或问题
- 已确定的结论或方案
- 未完成的任务
"""
# 3. 调用 LLM 摘要
summary = await self._llm_summarize(summary_prompt)
# 4. 创建摘要记忆
summary_entry = MemoryEntry(
id=str(uuid.uuid4()),
type=MemoryType.SEMANTIC,
content=summary,
metadata={"original_entries": len(entries)},
created_at=time.time(),
updated_at=time.time(),
importance=0.7,
)
await self.backend.save(summary_entry)
# 5. 删除原始记忆
for entry in entries:
await self.backend.delete(entry.id)
return summary
```
---
## 10. 平台适配Platform Adaptation
### 10.1 平台特性
```python
@dataclass
class PlatformCapabilities:
"""平台能力"""
supports_streaming: bool = False
max_message_length: int = 4096
supports_markdown: bool = True
supports_html: bool = False
supports_images: bool = True
supports_mentions: bool = True
supports_reply: bool = True
rate_limit_rpm: int = 60
rate_limit_rpd: int = 10000
PLATFORM_CAPABILITIES = {
"telegram": PlatformCapabilities(
supports_streaming=False,
max_message_length=4096,
supports_markdown=True,
supports_html=True,
),
"discord": PlatformCapabilities(
supports_streaming=False,
max_message_length=2000,
supports_markdown=True,
supports_html=False,
supports_reply=True,
),
"qq": PlatformCapabilities(
supports_streaming=False,
max_message_length=500,
supports_markdown=False,
supports_mentions=True,
),
"webchat": PlatformCapabilities(
supports_streaming=True,
max_message_length=10000,
supports_markdown=True,
supports_html=True,
),
}
```
### 10.2 策略选择器
```python
class PlatformStrategySelector:
"""平台策略选择器"""
def __init__(self, config: PlatformAdaptationConfig):
self.config = config
self.capabilities = PLATFORM_CAPABILITIES
def select_strategy(
self,
platform: str,
content_length: int,
user_preference: str | None = None,
) -> OutputStrategy:
"""选择输出策略"""
caps = self.capabilities.get(platform)
# 1. 用户偏好优先
if user_preference and self._is_valid_strategy(user_preference, caps):
return OutputStrategy(user_preference)
# 2. 平台能力判断
if not caps:
return OutputStrategy.FULL
# 3. 平台配置覆盖
platform_strategy = self.config.strategy_by_platform.get(platform)
if platform_strategy:
return OutputStrategy(platform_strategy)
# 4. 内容长度判断
if content_length > caps.max_message_length:
return OutputStrategy.SEGMENTED
# 5. 流式支持判断
if caps.supports_streaming:
return OutputStrategy.STREAMING
return OutputStrategy.FULL
```
---
## 11. 配置汇总
### 11.1 agent.yaml 完整配置
```yaml
# Agent 配置
# 输入缓冲区
input_buffer:
max_queue_size: 1000
max_message_age: 3600
overflow_strategy: "drop_oldest"
overflow_hint: "[消息过多,部分早期消息已丢弃]"
# 流控
flow_control:
mode: "auto"
auto:
api_rpm_limit: 60
messages_per_request: 5
safety_margin: 0.8
min_interval: 0.5
max_interval: 10
# 上下文
context:
max_context_tokens: 128000
compress_threshold: 0.85
keep_recent_messages: 6
compress_instruction: |
请简洁地总结对话要点...
# 工具调用
tool_calling:
strategy: "smart"
max_calls_per_request: 128
timeout: 60
max_retries: 3
parallel_calls: true
max_parallel_calls: 5
# 安全
security:
injection:
enable: true
mode: "strict"
patterns: [...]
on_detect: "sanitize"
content_filter:
enable: true
level: "standard"
replacement: "[已过滤]"
leakage_prevention:
blocked_file_patterns: [...]
blocked_output_patterns: [...]
placeholder: "[REDACTED]"
# 输出
output:
default_strategy: "streaming"
streaming:
chunk_size: 20
chunk_interval: 0.05
segmented:
enable: true
threshold: 500
mode: "sentence"
split_regex: "[。!?;\n]+"
random_interval: "0.5,2.0"
add_ellipsis: true
platform_adaptation:
strategy_by_platform:
telegram: "segmented"
discord: "segmented"
webchat: "streaming"
max_length_by_platform:
telegram: 4096
discord: 2000
# 记忆
memory:
backend: "sqlite"
sqlite:
path: "$XDG_DATA_HOME/astrbot/state/memory.db"
retention:
working_memory_days: 7
auto_summary_threshold: 50
context_window:
recent_rounds: 10
```
---
## 12. 错误处理与恢复
### 12.1 错误分类
```python
class ErrorType(Enum):
"""错误类型"""
RATE_LIMIT = "rate_limit" # 限流
TIMEOUT = "timeout" # 超时
NETWORK = "network" # 网络错误
API = "api" # API 错误
TOOL = "tool" # 工具错误
SECURITY = "security" # 安全错误
INTERNAL = "internal" # 内部错误
@dataclass
class ErrorRecoveryConfig:
"""错误恢复配置"""
max_retries: dict[ErrorType, int] = field(default_factory=lambda: {
ErrorType.RATE_LIMIT: 5,
ErrorType.TIMEOUT: 3,
ErrorType.NETWORK: 3,
ErrorType.API: 2,
ErrorType.TOOL: 2,
ErrorType.SECURITY: 0,
ErrorType.INTERNAL: 1,
})
backoff_multiplier: float = 1.5
max_backoff: float = 60.0
```
### 12.2 错误处理策略
```python
async def handle_error(
error: Exception,
context: AgentContext,
config: ErrorRecoveryConfig,
) -> ErrorAction:
"""处理错误并决定下一步行动"""
error_type = classify_error(error)
retries = context.metadata.get(f"retry_{error_type.value}", 0)
if retries >= config.max_retries.get(error_type, 0):
return ErrorAction.FAIL
# 指数退避
if retries > 0:
backoff = min(
config.backoff_multiplier ** retries,
config.max_backoff
)
await asyncio.sleep(backoff)
context.metadata[f"retry_{error_type.value}"] = retries + 1
if error_type == ErrorType.RATE_LIMIT:
# 更新流控配置
flow_control.decrease_rate(0.8)
return ErrorAction.RETRY
elif error_type == ErrorType.SECURITY:
# 安全错误不重试
return ErrorAction.BLOCK
elif error_type == ErrorType.API:
# API 错误,检查是否可恢复
if is_retryable_api_error(error):
return ErrorAction.RETRY
return ErrorAction.FAIL
return ErrorAction.RETRY
class ErrorAction(Enum):
"""错误处理动作"""
RETRY = "retry"
FAIL = "fail"
BLOCK = "block"
FALLBACK = "fallback"
```
---
## 13. 扩展点
### 13.1 插件扩展点
```python
# 输入处理扩展
class InputBufferPlugin(ABC):
"""输入缓冲区插件"""
async def pre_add_message(
self,
message: InputMessage,
) -> InputMessage | None:
"""消息添加前拦截,返回 None 表示跳过"""
pass
async def post_add_message(
self,
message: InputMessage,
) -> None:
"""消息添加后处理"""
pass
# 输出处理扩展
class OutputBufferPlugin(ABC):
"""输出缓冲区插件"""
async def pre_send_message(
self,
message: OutputMessage,
) -> OutputMessage | None:
"""消息发送前拦截"""
pass
async def post_send_message(
self,
message: OutputMessage,
) -> None:
"""消息发送后处理"""
pass
# 安全扩展
class SecurityPlugin(ABC):
"""安全插件"""
async def check_injection(
self,
content: str,
) -> list[SecurityIssue]:
"""自定义注入检测"""
pass
async def filter_content(
self,
content: str,
) -> str:
"""自定义内容过滤"""
pass
```
### 13.2 调度器扩展
```python
# 自定义调度策略
class CustomScheduler(ABC):
"""自定义调度策略"""
async def select_next_message(
self,
queues: dict[str, UserMessageQueue],
) -> InputMessage | None:
"""选择下一条消息"""
pass
async def on_queue_empty(
self,
user_id: str,
) -> None:
"""队列为空时的处理"""
pass
```