refactor(Dockerfile): 使用requirements.txt替代硬编码依赖 将Dockerfile中的硬编码pip包列表替换为通过requirements.txt文件安装, 提高依赖管理的灵活性和可维护性。 feat(scheduling): 移除内置APScheduler,采用独立调度系统 移除app/core/background/scheduler.py中原来的APScheduler实现, 改为使用新的应用级调度系统app.application.scheduling。 refactor(task_queue): 调整任务队列模块结构和导入路径 将任务队列相关常量从app.core.background.task_queue.constants迁移至 app.tasks.constants,并更新所有相关导入路径和引用。 refactor(events): 将事件服务重构为独立的应用层组件 将事件分发逻辑从核心层迁移到应用层,使用app.application.events.EventDispatchService 替代原有的app.modules.events.services.EventService。 feat(ai_memory): 增强AI记忆自动写入的安全策略 新增ai_memory_blocked_content_terms配置项用于阻止敏感内容, 添加TTL过期机制控制自动写入条目的生命周期。 fix(security): 强化生产环境安全验证机制 增加model_validator确保生产环境中数据库连接、API密钥、CORS设置等 关键安全配置符合要求。 feat(risks): 优化风险事件操作动作的外键约束 为RiskEventAction模型的风险事件ID字段添加外键约束, 防止孤立记录并增强数据完整性。 refactor(audit): 优化审计服务方法命名和事务处理 将AuditService的log方法重命名为record以反映其阶段行为, 并调整事务提交时机以提高性能。 feat(events): 增强领域事件并发处理和响应模型 添加事件锁定机制防止重复处理,更新API响应模型以提供 更准确的数据类型定义。 ```
193 lines
6.7 KiB
Python
193 lines
6.7 KiB
Python
import re
|
||
from typing import Any
|
||
|
||
from fastapi import HTTPException
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.application.feishu.delivery import send_text_if_configured
|
||
from app.application.feishu.results import command_result
|
||
from app.core.config import get_settings
|
||
from app.modules.ai_memory.constants import AIMemoryStatus
|
||
from app.modules.ai_memory.service import AIMemoryService
|
||
from app.modules.feishu.constants import FeishuCommandName, FeishuReplyType
|
||
from app.modules.feishu.service import FeishuService
|
||
|
||
RULE_TITLE = "AI 学习规则"
|
||
RULE_CREATE_PATTERN = re.compile(r"^学习规则(?:\s+(\d{1,3}))?\s*[::]\s*(.*)$")
|
||
MARKET_RULE_CREATE_PATTERN = re.compile(
|
||
r"^学习市场规则(?:\s+(\d{1,3}))?\s*[::]\s*(.*)$"
|
||
)
|
||
RULE_DISABLE_PATTERN = re.compile(r"^停用规则\s+(MEM-[A-Za-z0-9-]+)$", re.IGNORECASE)
|
||
RULE_ENABLE_PATTERN = re.compile(r"^启用规则\s+(MEM-[A-Za-z0-9-]+)$", re.IGNORECASE)
|
||
RULE_LIST_COMMANDS = {"查看规则", "规则列表", "查看市场规则"}
|
||
RULE_COMMAND_PREFIXES = (
|
||
"学习市场规则",
|
||
"学习规则",
|
||
"查看市场规则",
|
||
"查看规则",
|
||
"规则列表",
|
||
"停用规则",
|
||
"启用规则",
|
||
)
|
||
RULE_COMMAND_HELP = (
|
||
"规则指令格式:\n"
|
||
"学习规则:<规则内容>\n"
|
||
"学习规则 80:<规则内容>\n"
|
||
"学习市场规则 80:<仅用于市场分析的规则内容>\n"
|
||
"查看规则\n"
|
||
"停用规则 <规则编号>\n"
|
||
"启用规则 <规则编号>"
|
||
)
|
||
|
||
|
||
def handle_rule_command(
|
||
db: Session,
|
||
feishu: FeishuService,
|
||
command_text: str,
|
||
chat_id: str | None,
|
||
actor: str,
|
||
auto_reply: bool,
|
||
) -> dict[str, Any] | None:
|
||
"""Handle persistent AI rule commands."""
|
||
|
||
if not command_text.startswith(RULE_COMMAND_PREFIXES):
|
||
return None
|
||
command = _command_name(command_text)
|
||
if command in {
|
||
FeishuCommandName.RULE_CREATE,
|
||
FeishuCommandName.RULE_DISABLE,
|
||
FeishuCommandName.RULE_ENABLE,
|
||
} and get_settings().read_only_mode:
|
||
return _result(
|
||
feishu,
|
||
command,
|
||
"当前为只读模式,不能新增或修改学习规则。请由管理员启用操作后重试。",
|
||
chat_id,
|
||
actor,
|
||
auto_reply,
|
||
)
|
||
|
||
content = RULE_COMMAND_HELP
|
||
try:
|
||
command, content = _execute(db, command_text, command, actor)
|
||
except HTTPException as exc:
|
||
detail = str(exc.detail)
|
||
if "secret-like" in detail:
|
||
content = "规则疑似包含密码、令牌或其他密钥信息,已拒绝学习。"
|
||
elif exc.status_code == 404:
|
||
content = "没有找到该规则,请先发送“查看规则”确认规则编号。"
|
||
elif "priority" in detail:
|
||
content = "规则优先级必须在 1 到 100 之间。"
|
||
else:
|
||
content = "规则未保存,请检查指令内容后重试。"
|
||
return _result(feishu, command, content, chat_id, actor, auto_reply)
|
||
|
||
|
||
def _execute(
|
||
db: Session,
|
||
command_text: str,
|
||
command: FeishuCommandName,
|
||
actor: str,
|
||
) -> tuple[FeishuCommandName, str]:
|
||
market_create_match = MARKET_RULE_CREATE_PATTERN.fullmatch(command_text)
|
||
create_match = market_create_match or RULE_CREATE_PATTERN.fullmatch(command_text)
|
||
disable_match = RULE_DISABLE_PATTERN.fullmatch(command_text)
|
||
enable_match = RULE_ENABLE_PATTERN.fullmatch(command_text)
|
||
memory = AIMemoryService(db)
|
||
if create_match:
|
||
return command, _create_rule(memory, create_match, market_create_match is not None, actor)
|
||
if command_text in RULE_LIST_COMMANDS:
|
||
return FeishuCommandName.RULE_LIST, _list_rules(memory, command_text)
|
||
if disable_match or enable_match:
|
||
enabled = enable_match is not None
|
||
match = enable_match or disable_match
|
||
rule = memory.update_rule(
|
||
code=match.group(1),
|
||
content=None,
|
||
priority=None,
|
||
tags=None,
|
||
enabled=enabled,
|
||
actor=actor,
|
||
)
|
||
state = "已启用" if enabled else "已停用"
|
||
return (
|
||
FeishuCommandName.RULE_ENABLE if enabled else FeishuCommandName.RULE_DISABLE,
|
||
f"规则{state}。\n"
|
||
f"编号:{rule['code']}\n"
|
||
f"优先级:{rule['importance']}\n"
|
||
f"范围:{rule['scope']} / {rule['subject']}\n"
|
||
f"状态:{state}",
|
||
)
|
||
return command, RULE_COMMAND_HELP
|
||
|
||
|
||
def _create_rule(
|
||
memory: AIMemoryService,
|
||
match: re.Match[str],
|
||
market_rule: bool,
|
||
actor: str,
|
||
) -> str:
|
||
priority = int(match.group(1) or 50)
|
||
content = match.group(2).strip()
|
||
if not content:
|
||
return f"规则内容不能为空。\n\n{RULE_COMMAND_HELP}"
|
||
if not 1 <= priority <= 100:
|
||
return "规则优先级必须在 1 到 100 之间。"
|
||
rule = memory.create_rule(
|
||
content=content,
|
||
scope="market" if market_rule else "global",
|
||
subject="market" if market_rule else "company",
|
||
priority=priority,
|
||
tags=["feishu", *(["market"] if market_rule else [])],
|
||
actor=actor,
|
||
)
|
||
return (
|
||
"规则已学习。\n"
|
||
f"编号:{rule['code']}\n"
|
||
f"优先级:{rule['importance']}\n"
|
||
f"范围:{rule['scope']} / {rule['subject']}\n"
|
||
"状态:已启用"
|
||
)
|
||
|
||
|
||
def _list_rules(memory: AIMemoryService, command_text: str) -> str:
|
||
rules = memory.list_rules(
|
||
scope="market" if command_text == "查看市场规则" else None,
|
||
status_filter=AIMemoryStatus.ACTIVE,
|
||
limit=20,
|
||
)
|
||
if not rules:
|
||
return "当前没有已启用的学习规则。"
|
||
lines = ["当前已启用的学习规则:"]
|
||
for rule in rules:
|
||
rule_text = str(rule["content"])
|
||
if len(rule_text) > 80:
|
||
rule_text = f"{rule_text[:80]}…"
|
||
lines.append(
|
||
f"{rule['code']}|优先级 {rule['importance']}|"
|
||
f"{rule['scope']}/{rule['subject']}\n{rule_text}"
|
||
)
|
||
return "\n\n".join(lines)
|
||
|
||
|
||
def _command_name(command_text: str) -> FeishuCommandName:
|
||
if command_text.startswith("停用规则"):
|
||
return FeishuCommandName.RULE_DISABLE
|
||
if command_text.startswith("启用规则"):
|
||
return FeishuCommandName.RULE_ENABLE
|
||
if command_text.startswith(("查看市场规则", "查看规则", "规则列表")):
|
||
return FeishuCommandName.RULE_LIST
|
||
return FeishuCommandName.RULE_CREATE
|
||
|
||
|
||
def _result(
|
||
feishu: FeishuService,
|
||
command: FeishuCommandName,
|
||
content: str,
|
||
chat_id: str | None,
|
||
actor: str,
|
||
auto_reply: bool,
|
||
) -> dict[str, Any]:
|
||
response = send_text_if_configured(feishu, chat_id, content, actor) if auto_reply else None
|
||
return command_result(command, FeishuReplyType.TEXT, RULE_TITLE, content, response)
|