feat: Phase 5 — AI Research Agent(受控工具白名单 + LLM 编排 + API)
- agent/tools.py:Tool 元数据(JSON Schema)+ 白名单调用(异常转可读反馈,不中断对话) - agent/tools_impl.py:6 个受控工具 search_stocks / get_market_data / test_factor / run_backtest / get_experiment / compare_experiments —— 全部只读经 Job/Experiment 链路,研究自动归档;无 shell/任意执行/写删数据能力 - agent/llm.py:LLMClient 抽象 + OpenAI 兼容客户端(LLM_API_KEY/LLM_BASE_URL/LLM_MODEL 走 .env,未配置给出引导提示)+ 研究纪律 system prompt(反过拟合/样本外/成本) - agent/service.py:编排循环(tool/final JSON 决策 → 执行 → 回喂 → 结论),轮次上限兜底,未知工具拒绝 - /api/agent/chat;httpx 移至主依赖;Job 默认工厂抽取(api/agent/executor 复用) - 测试 6 项(白名单无 shell、完整研究循环产出、未知工具拒绝、轮次兜底),全量 79 passed / ruff clean
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
"""AI Research Agent(Phase 5)。
|
||||
|
||||
Agent 是 Research Assistant(AGENT.md §28/§29):
|
||||
- 只能调用本目录 tools 提供的**白名单受控工具**(只读研究能力)
|
||||
- 禁止:执行任意 shell / 修改删除数据 / 修改配置与凭证
|
||||
- 研究行为:提出假设 → 建立实验 → 运行测试 → 分析 → 下一步,禁止以单次高收益宣告策略有效
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
TOOL_CALL_TAG = "__tool__"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Tool:
|
||||
"""受控工具元数据(LLM 可见的 JSON Schema + 调用实现)。"""
|
||||
|
||||
name: str
|
||||
description: str
|
||||
parameters: dict[str, Any]
|
||||
handler: Callable[[dict[str, Any]], str] = field(repr=False)
|
||||
|
||||
def schema(self) -> dict[str, Any]:
|
||||
return {
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"parameters": self.parameters,
|
||||
}
|
||||
|
||||
def invoke(self, args: dict[str, Any]) -> str:
|
||||
"""执行工具;任何异常都转为可读错误串(不向上抛,避免中断整轮对话)。"""
|
||||
try:
|
||||
result = self.handler(args)
|
||||
return result if isinstance(result, str) else json.dumps(result, ensure_ascii=False)
|
||||
except Exception as exc: # noqa: BLE001 —— 工具异常反馈给 LLM 而非崩溃
|
||||
return json.dumps({"error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False)
|
||||
|
||||
|
||||
def tools_schema(tools: list[Tool]) -> str:
|
||||
return json.dumps([t.schema() for t in tools], ensure_ascii=False, indent=1)
|
||||
Reference in New Issue
Block a user