智能体查询优化
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:
|
except Exception:
|
||||||
logger.exception("Skill ownership migration failed (non-fatal)")
|
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 调度器:常驻后台任务,每小时检查一次需不需要进化
|
# 启动 skill curator 调度器:常驻后台任务,每小时检查一次需不需要进化
|
||||||
# (per-user interval_hours 仍是真正决定要不要跑进化的闸)。
|
# (per-user interval_hours 仍是真正决定要不要跑进化的闸)。
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
"""Configuration and loaders for custom agents."""
|
"""Configuration and loaders for custom agents."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
import threading
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@@ -13,6 +15,25 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
SOUL_FILENAME = "SOUL.md"
|
SOUL_FILENAME = "SOUL.md"
|
||||||
AGENT_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]+$")
|
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;
|
# Backward-compatible alias. Historically this validated the display name;
|
||||||
# after the DB-backed agent table, display names can be Chinese and duplicated.
|
# after the DB-backed agent table, display names can be Chinese and duplicated.
|
||||||
# The pattern now applies only to storage/runtime ids.
|
# 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
|
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] = {}
|
data: dict[str, Any] = {}
|
||||||
if config_file.exists():
|
if config_file.exists():
|
||||||
try:
|
try:
|
||||||
@@ -112,7 +145,11 @@ def load_agent_config(agent_id: str | None) -> AgentConfig | None:
|
|||||||
known_fields = set(AgentConfig.model_fields.keys())
|
known_fields = set(AgentConfig.model_fields.keys())
|
||||||
data = {k: v for k, v in data.items() if k in known_fields}
|
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:
|
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
|
soul_path = agent_dir / SOUL_FILENAME
|
||||||
if not soul_path.exists():
|
if not soul_path.exists():
|
||||||
return None
|
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()
|
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]:
|
def list_custom_agents() -> list[AgentConfig]:
|
||||||
|
|||||||
Reference in new issue
Block a user