chore: opencode冗余清理 + LLM任务级模型选择 + systemd服务化
- 删除 opencode_search.py / mcp_search_server.py 及所有 MCP 引用 - 移除搜索缓存定时任务(scheduled_refresh_search_cache) - 清理前后端所有 opencode/MCP 代码和注释 - LLM 提供商量换:opencode-go→nvidia(默认)+sensenova(合规审查) - llm_configs 新增 is_default 字段,API 层互斥逻辑 - 所有定时任务支持独立 LLM 模型选择(LLM_TASK_PROVIDER env) - compliance_optimizer.py 修复:import os / 解硬编码 / 关键词过滤 - Scheduler 日志修复:始终 INSERT,避免僵尸 running 行 - Systemd 服务化:Restart=always / 单 worker / Type=exec - 搜索提供商:替换 opencode→360/搜狗/微信(免 Key) - 更新 AGENTS.md / PROGRESS.md
This commit is contained in:
@@ -35,11 +35,18 @@ _FALLBACK = {
|
||||
}
|
||||
|
||||
def _get_active_provider() -> str:
|
||||
"""从 DB 读取活跃供应商,DB 不可用时回退环境变量"""
|
||||
"""从 DB 读取活跃供应商,优先取 is_default=True;DB 不可用时回退环境变量"""
|
||||
try:
|
||||
from ..database import SessionLocal
|
||||
from ..models import LLMConfig
|
||||
db = SessionLocal()
|
||||
# 优先取默认
|
||||
default = db.query(LLMConfig).filter(
|
||||
LLMConfig.is_default == True, LLMConfig.is_active == True
|
||||
).first()
|
||||
if default and default.provider:
|
||||
db.close()
|
||||
return default.provider
|
||||
active = db.query(LLMConfig).filter(LLMConfig.is_active == True).first()
|
||||
db.close()
|
||||
if active and active.provider:
|
||||
@@ -58,7 +65,7 @@ def _get_provider_config(provider: Optional[str] = None) -> dict:
|
||||
from ..database import SessionLocal
|
||||
from ..models import LLMConfig
|
||||
db = SessionLocal()
|
||||
cfg = db.query(LLMConfig).filter(LLMConfig.provider == p).order_by(LLMConfig.is_active.desc()).first()
|
||||
cfg = db.query(LLMConfig).filter(LLMConfig.provider == p).order_by(LLMConfig.is_default.desc(), LLMConfig.is_active.desc()).first()
|
||||
if cfg:
|
||||
db_model = cfg.model
|
||||
db_base_url = cfg.base_url
|
||||
@@ -107,7 +114,7 @@ def _get_provider_fallback_list() -> List[str]:
|
||||
return providers
|
||||
except Exception:
|
||||
pass
|
||||
return ["opencode-go", "nvidia"]
|
||||
return ["nvidia", "sensenova", "opencode-go"]
|
||||
|
||||
def call_llm(
|
||||
prompt: str,
|
||||
@@ -128,6 +135,10 @@ def call_llm(
|
||||
system_prompt = system_prompt if system_prompt is not None else defaults["system_prompt"]
|
||||
|
||||
providers_to_try = [provider] if provider else _get_provider_fallback_list()
|
||||
# LLM_TASK_PROVIDER 环境变量可覆盖任务级别的模型选择
|
||||
if not provider and os.getenv("LLM_TASK_PROVIDER"):
|
||||
task_provider = os.getenv("LLM_TASK_PROVIDER")
|
||||
providers_to_try = [task_provider] + [p for p in providers_to_try if p != task_provider]
|
||||
last_error = None
|
||||
for p in providers_to_try:
|
||||
try:
|
||||
|
||||
@@ -19,8 +19,25 @@ from .collector import run_collector_blocking
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def _set_task_llm_provider(module_id: str):
|
||||
"""从 TaskConfig 读取 llm_provider 并设为环境变量,供子进程和 call_llm 读取"""
|
||||
try:
|
||||
from ..database import SessionLocal
|
||||
from ..models import TaskConfig
|
||||
db = SessionLocal()
|
||||
cfg = db.query(TaskConfig).filter(TaskConfig.module_id == module_id).first()
|
||||
db.close()
|
||||
if cfg and cfg.params:
|
||||
provider = cfg.params.get("llm_provider")
|
||||
if provider:
|
||||
os.environ["LLM_TASK_PROVIDER"] = provider
|
||||
logger.debug("[%s] LLM provider set to %s", module_id, provider)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
os.environ.pop("LLM_TASK_PROVIDER", None)
|
||||
|
||||
MODULES = {
|
||||
"scheduled_refresh_search_cache": {"name": "🔍 搜索缓存", "cron": "01:00"},
|
||||
"scheduled_fetch_trends": {"name": "🔥 热点趋势", "cron": "01:10"},
|
||||
"scheduled_collect": {"name": "📡 内容采集", "cron": "01:30"},
|
||||
"scheduled_generate": {"name": "🤖 内容创作", "cron": "02:00"},
|
||||
@@ -32,7 +49,6 @@ MODULES = {
|
||||
}
|
||||
|
||||
LOG_FILE_MAP = {
|
||||
"scheduled_refresh_search_cache": "opencode_search",
|
||||
"scheduled_fetch_trends": "trends",
|
||||
"scheduled_collect": "collector",
|
||||
"scheduled_generate": "creator",
|
||||
@@ -149,7 +165,6 @@ class TaskScheduler:
|
||||
db.close()
|
||||
|
||||
MODULE_JOBS = [
|
||||
("scheduled_refresh_search_cache", self._run_refresh_search_cache, "搜索缓存"),
|
||||
("scheduled_fetch_trends", self._run_fetch_trends, "热点趋势"),
|
||||
("scheduled_collect", self._run_collect, "内容采集"),
|
||||
("scheduled_generate", self._run_generate, "内容创作"),
|
||||
@@ -200,6 +215,7 @@ class TaskScheduler:
|
||||
|
||||
def _run_fetch_trends(self):
|
||||
"""定时刷新热点趋势(百度/微博/知乎实时热搜 + LLM补充)"""
|
||||
_set_task_llm_provider("scheduled_fetch_trends")
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_fetch_trends", "running", started_at=started)
|
||||
try:
|
||||
@@ -229,48 +245,8 @@ class TaskScheduler:
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.exception("[Scheduled] Trends refresh error: %s", e)
|
||||
|
||||
def _run_refresh_search_cache(self):
|
||||
"""定时刷新搜索缓存(通过 opencode webfetch)"""
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_refresh_search_cache", "running", started_at=started)
|
||||
try:
|
||||
logger.info("[Scheduled] Refreshing search cache via opencode...")
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(PROJECT_ROOT / "scripts" / "opencode_search.py"), "--refresh-cache"],
|
||||
capture_output=True, text=True, timeout=600
|
||||
)
|
||||
for line in result.stdout.strip().split("\n"):
|
||||
if line.strip():
|
||||
logger.info("[SearchCache] %s", line.strip())
|
||||
for line in result.stderr.strip().split("\n"):
|
||||
if line.strip():
|
||||
logger.warning("[SearchCache] %s", line.strip())
|
||||
if result.returncode == 0:
|
||||
_log_task("scheduled_refresh_search_cache", "success", log_id=log_id,
|
||||
message="搜索缓存刷新成功",
|
||||
result_data={"output_lines": len(result.stdout.splitlines())},
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.info("[Scheduled] Search cache refreshed")
|
||||
else:
|
||||
_log_task("scheduled_refresh_search_cache", "failed", log_id=log_id,
|
||||
message="部分失败",
|
||||
error_trace=result.stderr[-500:],
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.warning("[Scheduled] Search cache refresh may have partial failures")
|
||||
except subprocess.TimeoutExpired:
|
||||
_log_task("scheduled_refresh_search_cache", "failed", log_id=log_id,
|
||||
message="超时",
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.warning("[Scheduled] Search cache refresh timed out")
|
||||
except Exception as e:
|
||||
_log_task("scheduled_refresh_search_cache", "failed", log_id=log_id,
|
||||
message=str(e),
|
||||
error_trace=traceback.format_exc(),
|
||||
started_at=started, finished_at=datetime.now(timezone.utc))
|
||||
logger.exception("[Scheduled] Search cache refresh error: %s", e)
|
||||
|
||||
def _run_generate(self):
|
||||
_set_task_llm_provider("scheduled_generate")
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_generate", "running", started_at=started)
|
||||
try:
|
||||
@@ -296,6 +272,7 @@ class TaskScheduler:
|
||||
logger.exception("[Scheduled] Generation pipeline failed: %s", e)
|
||||
|
||||
def _run_optimize(self):
|
||||
_set_task_llm_provider("scheduled_optimize")
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_optimize", "running", started_at=started)
|
||||
try:
|
||||
@@ -314,6 +291,7 @@ class TaskScheduler:
|
||||
logger.exception("[Scheduled] Review failed: %s", e)
|
||||
|
||||
def _run_collect(self):
|
||||
_set_task_llm_provider("scheduled_collect")
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_collect", "running", started_at=started)
|
||||
try:
|
||||
@@ -334,6 +312,7 @@ class TaskScheduler:
|
||||
|
||||
def _run_optimize_sources(self, triggered_by="scheduler"):
|
||||
"""AI自动优化采集类别与信息源:对比市场热点和当前配置,给出调整建议"""
|
||||
_set_task_llm_provider("scheduled_optimize_sources")
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_optimize_sources", "running", started_at=started, triggered_by=triggered_by)
|
||||
try:
|
||||
@@ -397,6 +376,7 @@ class TaskScheduler:
|
||||
|
||||
def _run_metrics_sync(self, triggered_by="scheduler"):
|
||||
"""定时从各平台公开API获取发布文章的效果数据(当前仅支持知乎)"""
|
||||
_set_task_llm_provider("scheduled_metrics_sync")
|
||||
started = datetime.now(timezone.utc)
|
||||
log_id = _log_task("scheduled_metrics_sync", "running", started_at=started, triggered_by=triggered_by)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user