```
feat: 添加AI记忆模块和事件调度系统 - 新增AI记忆模块,支持本地记忆召回和自动写入功能 - 实现事件调度系统,支持批量处理待定事件和重试机制 - 集成心跳监控机制,跟踪API、调度器和工作节点状态 - 扩展仪表板数据统计,包含AI记忆条目和心跳概要 - 添加企业运营分析报告功能,提供财务、采购等多维度分析 - 更新配置设置,增加事件调度和AI记忆相关参数 - 优化任务队列,添加事件分发任务类型 - 扩展审计日志,记录AI记忆操作和事件调度行为 - 实现领域事件模型,支持事件持久化和状态管理 - 添加观察性服务,监控系统组件健康状况 ```
This commit is contained in:
@@ -80,6 +80,34 @@ class Settings(BaseSettings):
|
||||
legacy_project_sync_cron_minute: int = 0
|
||||
legacy_task_sync_cron_hour: int = 2
|
||||
legacy_task_sync_cron_minute: int = 30
|
||||
event_dispatch_enabled: bool = True
|
||||
event_dispatch_batch_size: int = 100
|
||||
event_dispatch_max_attempts: int = 3
|
||||
event_dispatch_retry_delay_seconds: int = 300
|
||||
event_dispatch_lock_seconds: int = 300
|
||||
event_dispatch_cron_minute: str = "*/5"
|
||||
heartbeat_interval_seconds: int = 60
|
||||
ai_memory_enabled: bool = True
|
||||
ai_memory_auto_write_enabled: bool = True
|
||||
ai_memory_recall_limit: int = 5
|
||||
ai_memory_forbidden_keys: list[str] = Field(
|
||||
default_factory=lambda: [
|
||||
"authorization",
|
||||
"api_key",
|
||||
"apikey",
|
||||
"access_token",
|
||||
"tenant_access_token",
|
||||
"token",
|
||||
"secret",
|
||||
"password",
|
||||
"openclaw_gateway_token",
|
||||
"hermes_api_key",
|
||||
"direct_llm_api_key",
|
||||
"feishu_app_secret",
|
||||
"feishu_verification_token",
|
||||
]
|
||||
)
|
||||
|
||||
@field_validator("cors_origins", mode="before")
|
||||
@classmethod
|
||||
def parse_cors_origins(cls, value: Any) -> list[str]:
|
||||
@@ -98,6 +126,7 @@ class Settings(BaseSettings):
|
||||
@field_validator(
|
||||
"openclaw_allowed_tools",
|
||||
"openclaw_allowed_actions",
|
||||
"ai_memory_forbidden_keys",
|
||||
mode="before",
|
||||
)
|
||||
@classmethod
|
||||
|
||||
@@ -6,6 +6,7 @@ class ActorValue(StrEnum):
|
||||
AUDITOR = "auditor"
|
||||
SYSTEM = "system"
|
||||
SCHEDULER = "scheduler"
|
||||
WORKER = "worker"
|
||||
FEISHU = "feishu"
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from socket import gethostname
|
||||
from typing import Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
|
||||
from app.core.constants import ActorValue
|
||||
from app.core.config import get_settings
|
||||
from app.core.constants import ActorValue
|
||||
from app.modules.feishu.constants import FeishuReceiveIdType
|
||||
from app.modules.observability.constants import HeartbeatComponent
|
||||
|
||||
|
||||
def attach_scheduler(app: FastAPI) -> None:
|
||||
@@ -12,35 +16,53 @@ def attach_scheduler(app: FastAPI) -> None:
|
||||
if not settings.scheduler_enabled:
|
||||
return
|
||||
|
||||
scheduler = create_scheduler(app)
|
||||
|
||||
@app.on_event("startup")
|
||||
def start_scheduler() -> None:
|
||||
scheduler.start()
|
||||
|
||||
@app.on_event("shutdown")
|
||||
def stop_scheduler() -> None:
|
||||
scheduler.shutdown(wait=False)
|
||||
|
||||
|
||||
def create_scheduler(app: FastAPI | None = None) -> Any:
|
||||
"""Create the V2/V3 scheduler without requiring a FastAPI process."""
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.task_queue import (
|
||||
enqueue_daily_brief_push,
|
||||
enqueue_event_dispatch,
|
||||
enqueue_legacy_project_sync,
|
||||
enqueue_legacy_task_sync,
|
||||
enqueue_project_weekly_push,
|
||||
)
|
||||
from app.modules.observability.service import ObservabilityService
|
||||
from app.modules.reports.service import ReportService
|
||||
|
||||
settings = get_settings()
|
||||
scheduler = BackgroundScheduler(timezone="Asia/Shanghai")
|
||||
|
||||
def run_daily_brief() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
report = ReportService(db).daily_brief()
|
||||
app.state.last_daily_brief = report
|
||||
_set_state(app, "last_daily_brief", report)
|
||||
if (
|
||||
settings.feishu_app_id
|
||||
and settings.feishu_app_secret
|
||||
and settings.feishu_default_chat_id
|
||||
):
|
||||
if settings.task_queue_enabled:
|
||||
app.state.last_daily_brief_dispatch = enqueue_daily_brief_push(
|
||||
dispatch = enqueue_daily_brief_push(
|
||||
receive_id=settings.feishu_default_chat_id,
|
||||
receive_id_type=FeishuReceiveIdType.CHAT_ID,
|
||||
actor=ActorValue.SCHEDULER,
|
||||
)
|
||||
_set_state(app, "last_daily_brief_dispatch", dispatch)
|
||||
return
|
||||
ReportService(db).push_report(
|
||||
report,
|
||||
@@ -55,18 +77,19 @@ def attach_scheduler(app: FastAPI) -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
report = ReportService(db).project_weekly()
|
||||
app.state.last_project_weekly = report
|
||||
_set_state(app, "last_project_weekly", report)
|
||||
if (
|
||||
settings.feishu_app_id
|
||||
and settings.feishu_app_secret
|
||||
and settings.feishu_default_chat_id
|
||||
):
|
||||
if settings.task_queue_enabled:
|
||||
app.state.last_project_weekly_dispatch = enqueue_project_weekly_push(
|
||||
dispatch = enqueue_project_weekly_push(
|
||||
receive_id=settings.feishu_default_chat_id,
|
||||
receive_id_type=FeishuReceiveIdType.CHAT_ID,
|
||||
actor=ActorValue.SCHEDULER,
|
||||
)
|
||||
_set_state(app, "last_project_weekly_dispatch", dispatch)
|
||||
return
|
||||
ReportService(db).push_report(
|
||||
report,
|
||||
@@ -78,14 +101,31 @@ def attach_scheduler(app: FastAPI) -> None:
|
||||
db.close()
|
||||
|
||||
def run_legacy_project_sync() -> None:
|
||||
app.state.last_legacy_project_sync_dispatch = enqueue_legacy_project_sync(
|
||||
actor=ActorValue.SCHEDULER,
|
||||
)
|
||||
dispatch = enqueue_legacy_project_sync(actor=ActorValue.SCHEDULER)
|
||||
_set_state(app, "last_legacy_project_sync_dispatch", dispatch)
|
||||
|
||||
def run_legacy_task_sync() -> None:
|
||||
app.state.last_legacy_task_sync_dispatch = enqueue_legacy_task_sync(
|
||||
dispatch = enqueue_legacy_task_sync(actor=ActorValue.SCHEDULER)
|
||||
_set_state(app, "last_legacy_task_sync_dispatch", dispatch)
|
||||
|
||||
def run_event_dispatch() -> None:
|
||||
dispatch = enqueue_event_dispatch(
|
||||
limit=settings.event_dispatch_batch_size,
|
||||
actor=ActorValue.SCHEDULER,
|
||||
)
|
||||
_set_state(app, "last_event_dispatch", dispatch)
|
||||
|
||||
def record_scheduler_heartbeat() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
heartbeat = ObservabilityService(db).record_heartbeat(
|
||||
component=HeartbeatComponent.SCHEDULER,
|
||||
instance_id=gethostname(),
|
||||
actor=ActorValue.SCHEDULER,
|
||||
)
|
||||
_set_state(app, "last_scheduler_heartbeat", heartbeat)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
scheduler.add_job(
|
||||
run_daily_brief,
|
||||
@@ -104,6 +144,21 @@ def attach_scheduler(app: FastAPI) -> None:
|
||||
id="project_weekly_push",
|
||||
replace_existing=True,
|
||||
)
|
||||
if settings.event_dispatch_enabled:
|
||||
scheduler.add_job(
|
||||
run_event_dispatch,
|
||||
trigger="cron",
|
||||
minute=settings.event_dispatch_cron_minute,
|
||||
id="event_dispatch",
|
||||
replace_existing=True,
|
||||
)
|
||||
scheduler.add_job(
|
||||
record_scheduler_heartbeat,
|
||||
trigger="interval",
|
||||
seconds=settings.heartbeat_interval_seconds,
|
||||
id="scheduler_heartbeat",
|
||||
replace_existing=True,
|
||||
)
|
||||
if settings.legacy_sync_enabled and settings.legacy_project_query:
|
||||
scheduler.add_job(
|
||||
run_legacy_project_sync,
|
||||
@@ -122,11 +177,9 @@ def attach_scheduler(app: FastAPI) -> None:
|
||||
id="legacy_task_sync",
|
||||
replace_existing=True,
|
||||
)
|
||||
return scheduler
|
||||
|
||||
@app.on_event("startup")
|
||||
def start_scheduler() -> None:
|
||||
scheduler.start()
|
||||
|
||||
@app.on_event("shutdown")
|
||||
def stop_scheduler() -> None:
|
||||
scheduler.shutdown(wait=False)
|
||||
def _set_state(app: FastAPI | None, key: str, value: Any) -> None:
|
||||
if app is not None:
|
||||
setattr(app.state, key, value)
|
||||
|
||||
@@ -9,6 +9,7 @@ TASK_PUSH_PROJECT_WEEKLY = "reports.push_project_weekly"
|
||||
TASK_GENERATE_RISK_EVENTS = "risks.generate_events"
|
||||
TASK_SYNC_LEGACY_PROJECTS = "legacy.sync_projects"
|
||||
TASK_SYNC_LEGACY_TASKS = "legacy.sync_tasks"
|
||||
TASK_DISPATCH_PENDING_EVENTS = "events.dispatch_pending"
|
||||
|
||||
|
||||
def dispatch_task(
|
||||
@@ -191,6 +192,31 @@ def enqueue_risk_event_generation(actor: str = "scheduler") -> dict[str, Any]:
|
||||
)
|
||||
|
||||
|
||||
def enqueue_event_dispatch(
|
||||
limit: int | None = None,
|
||||
actor: str = "scheduler",
|
||||
) -> dict[str, Any]:
|
||||
def inline() -> Any:
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.events.service import EventService
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
return EventService(db).dispatch_pending(
|
||||
limit=limit or get_settings().event_dispatch_batch_size,
|
||||
worker_id=actor,
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
return dispatch_task(
|
||||
TASK_DISPATCH_PENDING_EVENTS,
|
||||
{"limit": limit, "actor": actor},
|
||||
inline,
|
||||
)
|
||||
|
||||
|
||||
def enqueue_legacy_project_sync(
|
||||
source_query: str | None = None,
|
||||
source_query_name: str | None = None,
|
||||
|
||||
Reference in New Issue
Block a user