Files
Simon d9be75a98f 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
2026-09-06 17:22:05 +08:00

46 lines
1.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)