智能体查询优化
This commit is contained in:
1 parent
3a2b5d5ccd
commit
ef1481be63
2 files changed
+70
-2
No files matched your search
@@ -503,6 +503,24 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
except Exception:
|
||||
logger.exception("Skill ownership migration failed (non-fatal)")
|
||||
|
||||
# 一次性把 .deer-flow/agents/{id}/ 目录里历史遗留的 agent 导入 agents 表。
|
||||
# 路由层原本每次请求都跑一遍这个 sync(扫文件系统 + N 个 agent 各一次 DB
|
||||
# SELECT/UPDATE),把它挪到启动期 + 路由层只保留 TTL 兜底,显著降低
|
||||
# /api/agents 列表延迟。
|
||||
try:
|
||||
from app.gateway.routers.agents import (
|
||||
_mark_legacy_sync_fresh,
|
||||
_sync_legacy_agents,
|
||||
)
|
||||
|
||||
agent_store = getattr(app.state, "agent_store", None)
|
||||
if agent_store is not None:
|
||||
await _sync_legacy_agents(agent_store)
|
||||
_mark_legacy_sync_fresh()
|
||||
logger.info("Legacy folder-backed agents synced at startup")
|
||||
except Exception:
|
||||
logger.exception("Legacy agent sync failed at startup (non-fatal)")
|
||||
|
||||
# 启动 skill curator 调度器:常驻后台任务,每小时检查一次需不需要进化
|
||||
# (per-user interval_hours 仍是真正决定要不要跑进化的闸)。
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""Configuration and loaders for custom agents."""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
@@ -13,6 +15,25 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
SOUL_FILENAME = "SOUL.md"
|
||||
AGENT_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$")
|
||||
|
||||
# Per-file mtime caches. These are read on every /api/agents list/detail request
|
||||
# (one read per agent) — caching keyed by (path, mtime_ns, size) is correct
|
||||
# because any edit to the file updates mtime, and an external tool re-writing
|
||||
# the same bytes doesn't actually change the result we'd return. Cache size is
|
||||
# unbounded but lookup is O(1) and entries are tiny (one parsed dict / string
|
||||
# per agent file); for the order of N≤10⁴ agents the memory cost is negligible.
|
||||
_config_cache: dict[str, tuple[tuple[int, int], "AgentConfig | None"]] = {}
|
||||
_soul_cache: dict[str, tuple[tuple[int, int], str | None]] = {}
|
||||
_cache_lock = threading.Lock()
|
||||
|
||||
|
||||
def _file_stat_key(path) -> tuple[int, int] | None:
|
||||
"""Return (mtime_ns, size) for cache invalidation, or None when missing."""
|
||||
try:
|
||||
st = os.stat(path)
|
||||
except OSError:
|
||||
return None
|
||||
return (st.st_mtime_ns, st.st_size)
|
||||
# Backward-compatible alias. Historically this validated the display name;
|
||||
# after the DB-backed agent table, display names can be Chinese and duplicated.
|
||||
# The pattern now applies only to storage/runtime ids.
|
||||
@@ -94,6 +115,18 @@ def load_agent_config(agent_id: str | None) -> AgentConfig | None:
|
||||
)
|
||||
return None
|
||||
|
||||
# mtime-based cache. The config file is rewritten every time the agent is
|
||||
# edited via PUT /api/agents/{id}, which bumps mtime — so the next read
|
||||
# picks up the change automatically. Missing config_file = key None below,
|
||||
# bypassing the cache (cheap path: just return AgentConfig with id/name).
|
||||
cache_path_key = str(config_file)
|
||||
stat_key = _file_stat_key(config_file) if config_file.exists() else None
|
||||
if stat_key is not None:
|
||||
with _cache_lock:
|
||||
cached = _config_cache.get(cache_path_key)
|
||||
if cached is not None and cached[0] == stat_key:
|
||||
return cached[1]
|
||||
|
||||
data: dict[str, Any] = {}
|
||||
if config_file.exists():
|
||||
try:
|
||||
@@ -112,7 +145,11 @@ def load_agent_config(agent_id: str | None) -> AgentConfig | None:
|
||||
known_fields = set(AgentConfig.model_fields.keys())
|
||||
data = {k: v for k, v in data.items() if k in known_fields}
|
||||
|
||||
return AgentConfig(**data)
|
||||
result = AgentConfig(**data)
|
||||
if stat_key is not None:
|
||||
with _cache_lock:
|
||||
_config_cache[cache_path_key] = (stat_key, result)
|
||||
return result
|
||||
|
||||
|
||||
def load_agent_soul(agent_id: str | None) -> str | None:
|
||||
@@ -131,8 +168,21 @@ def load_agent_soul(agent_id: str | None) -> str | None:
|
||||
soul_path = agent_dir / SOUL_FILENAME
|
||||
if not soul_path.exists():
|
||||
return None
|
||||
# mtime-based cache, same rationale as load_agent_config: SOUL.md is
|
||||
# rewritten on edit (PUT /api/agents/{id}), which bumps mtime.
|
||||
cache_path_key = str(soul_path)
|
||||
stat_key = _file_stat_key(soul_path)
|
||||
if stat_key is not None:
|
||||
with _cache_lock:
|
||||
cached = _soul_cache.get(cache_path_key)
|
||||
if cached is not None and cached[0] == stat_key:
|
||||
return cached[1]
|
||||
content = soul_path.read_text(encoding="utf-8").strip()
|
||||
return content or None
|
||||
result = content or None
|
||||
if stat_key is not None:
|
||||
with _cache_lock:
|
||||
_soul_cache[cache_path_key] = (stat_key, result)
|
||||
return result
|
||||
|
||||
|
||||
def list_custom_agents() -> list[AgentConfig]:
|
||||
|
||||
Reference in new issue
Block a user