feat(ai): AI 生成自定义信号条件

- 新增 /api/signals/ai/generate 接口:自然语言描述 → 结构化信号条件
- 新增 custom_signals_ai 模块:组装提示词 + 解析校验 AI 返回的 JSON
- 复用 custom_signals.validate() 白名单安全闸门
- 新增输出 token 上限和上下文窗口设置
- 实现 AI 请求 max_tokens 钳制和输入预算检查
- 自定义信号右值字段名容错处理
- 增强 JSON 解析容错(尾随逗号、垃圾字符)
- 前端自定义信号对话框接入 AI 生成
This commit is contained in:
dev
2026-08-21 09:55:24 +08:00
parent 9b9538a70f
commit 8519a2bd16
18 changed files with 1255 additions and 25 deletions
+24
View File
@@ -58,6 +58,22 @@ def _invalidate_strategy_runtime(request: Request) -> None:
monitor_engine.invalidate_strategy_state()
def _missing_custom_signals(data_dir: Path, required_features) -> list[str]:
"""required_features 中 csg_ 列对应信号未定义的部分 (保存策略前校验)。
自定义信号列 (csg_ 前缀) 只有在 data/user_data/custom_signals/*.json
有对应定义时才会被注入; 引用不存在的信号运行必报缺列错, 保存时早失败。
"""
from app.strategy import custom_signals
defined = {s.get("id") for s in custom_signals.load_all(data_dir)}
return [
name for name in (required_features or ())
if isinstance(name, str) and name.startswith(custom_signals.PREFIX)
and name[len(custom_signals.PREFIX):] not in defined
]
def _cleanup_deleted_strategy(request: Request, strategy_id: str) -> list[str]:
"""尽力清理删除后的派生状态, 清理失败不应把已成功的源文件删除变成 500。"""
from app.services import preferences
@@ -669,6 +685,14 @@ def _save_strategy_code(req: StrategyCodeSaveRequest, request: Request, *, legac
raise ValueError("策略加载到了非预期文件,请检查是否存在重复 strategy_id")
if loaded.source != expected_source:
raise ValueError(f"策略来源异常: 期望 {expected_source}, 实际 {loaded.source}")
# 自定义信号存在性校验: REQUIRED_FEATURES 里 csg_ 列必须已有定义,
# 否则运行必报缺列错。早失败并恢复文件, 提示用户先创建信号。
missing = _missing_custom_signals(data_dir, loaded.required_features)
if missing:
raise ValueError(
"策略引用了未定义的自定义信号: " + ", ".join(sorted(missing))
+ " — 请先在「自定义信号」管理中创建对应信号后再保存"
)
except Exception as e:
_restore_strategy_file(path, previous_code)
engine.reload()