273 lines
9.5 KiB
Python
273 lines
9.5 KiB
Python
|
|
"""
|
|||
|
|
新工具自动发现与集成 — 扫描内外工具源,自动评估和生成集成方案
|
|||
|
|
"""
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
import logging
|
|||
|
|
import os
|
|||
|
|
import json
|
|||
|
|
from typing import Any, Dict, List, Optional
|
|||
|
|
|
|||
|
|
from app.services.llm_service import llm_service
|
|||
|
|
|
|||
|
|
logger = logging.getLogger(__name__)
|
|||
|
|
|
|||
|
|
TOOL_DISCOVERY_PROMPT = """你是一个工具集成分析专家。分析以下新工具是否适合集成到 AI Agent 平台。
|
|||
|
|
|
|||
|
|
当前平台能力:
|
|||
|
|
- Agent 工作流编排 (DAG)
|
|||
|
|
- LLM 调用 (多模型支持)
|
|||
|
|
- API 调用节点
|
|||
|
|
- 代码执行节点 (Python/JS)
|
|||
|
|
- 数据库查询节点
|
|||
|
|
- 条件分支节点
|
|||
|
|
- 通知节点 (飞书/Email)
|
|||
|
|
|
|||
|
|
新工具信息:
|
|||
|
|
{tool_info}
|
|||
|
|
|
|||
|
|
请分析:
|
|||
|
|
1. 匹配度 (0-1): 该工具与平台现有能力的互补程度
|
|||
|
|
2. 适用场景: 什么情况下 Agent 需要使用此工具
|
|||
|
|
3. 集成复杂度: low/medium/high
|
|||
|
|
4. 建议的 adapter 类型: api_wrapper / code_executor / plugin
|
|||
|
|
5. 集成方案: 简要描述如何接入
|
|||
|
|
6. 潜在风险: 使用此工具需要注意的问题
|
|||
|
|
|
|||
|
|
返回 JSON 格式:
|
|||
|
|
{{"match_score": 0.8, "scenarios": ["场景1"], "complexity": "medium", "adapter_type": "api_wrapper", "integration_plan": "方案描述", "risks": ["风险1"]}}
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ToolDiscovery:
|
|||
|
|
"""新工具自动发现与集成"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self._external_sources = [
|
|||
|
|
{"name": "github_trending", "url": "https://github.com/trending/python?since=weekly"},
|
|||
|
|
{"name": "mcp_marketplace", "url": "https://github.com/modelcontextprotocol/servers"},
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
def scan_internal_tools(self, tools_dir: str = "") -> List[Dict[str, Any]]:
|
|||
|
|
"""扫描平台内部 tools 目录,发现未注册的工具。"""
|
|||
|
|
if not tools_dir:
|
|||
|
|
base = os.path.dirname(os.path.dirname(__file__))
|
|||
|
|
tools_dir = os.path.join(base, "tools")
|
|||
|
|
if not os.path.isdir(tools_dir):
|
|||
|
|
tools_dir = os.path.join(os.path.dirname(base), "tools")
|
|||
|
|
|
|||
|
|
discovered = []
|
|||
|
|
if not os.path.isdir(tools_dir):
|
|||
|
|
return discovered
|
|||
|
|
|
|||
|
|
from app.services.tool_registry import tool_registry
|
|||
|
|
|
|||
|
|
for filename in os.listdir(tools_dir):
|
|||
|
|
if filename.startswith("_") or filename.startswith("__"):
|
|||
|
|
continue
|
|||
|
|
if filename.endswith(".py") and filename != "__init__.py":
|
|||
|
|
tool_name = filename[:-3]
|
|||
|
|
registered = tool_registry.get(tool_name) if hasattr(tool_registry, 'get') else None
|
|||
|
|
discovered.append({
|
|||
|
|
"tool_name": tool_name,
|
|||
|
|
"source": "internal",
|
|||
|
|
"registered": registered is not None,
|
|||
|
|
"file_path": os.path.join(tools_dir, filename),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return discovered
|
|||
|
|
|
|||
|
|
def scan_external_sources(self) -> List[Dict[str, Any]]:
|
|||
|
|
"""扫描外部工具源(GitHub trending, MCP marketplace)。"""
|
|||
|
|
discovered = []
|
|||
|
|
for source in self._external_sources:
|
|||
|
|
try:
|
|||
|
|
import urllib.request
|
|||
|
|
req = urllib.request.Request(source["url"], headers={"User-Agent": "AI-Agent-Platform"})
|
|||
|
|
# 只记录源信息,实际爬取在 evaluate_and_rank 中按需进行
|
|||
|
|
discovered.append({
|
|||
|
|
"source_name": source["name"],
|
|||
|
|
"url": source["url"],
|
|||
|
|
"status": "available",
|
|||
|
|
})
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.warning("外部源 %s 不可用: %s", source["name"], e)
|
|||
|
|
discovered.append({
|
|||
|
|
"source_name": source["name"],
|
|||
|
|
"url": source["url"],
|
|||
|
|
"status": "unavailable",
|
|||
|
|
"error": str(e),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return discovered
|
|||
|
|
|
|||
|
|
def evaluate_tool(self, tool_name: str, tool_description: str = "",
|
|||
|
|
tool_source: str = "", tool_docs: str = "") -> Dict[str, Any]:
|
|||
|
|
"""评估单个工具的集成价值(使用 LLM)。"""
|
|||
|
|
tool_info = f"""
|
|||
|
|
工具名称: {tool_name}
|
|||
|
|
来源: {tool_source}
|
|||
|
|
描述: {tool_description}
|
|||
|
|
文档/代码摘要: {tool_docs[:3000]}
|
|||
|
|
"""
|
|||
|
|
prompt = TOOL_DISCOVERY_PROMPT.format(tool_info=tool_info)
|
|||
|
|
|
|||
|
|
result = {
|
|||
|
|
"tool_name": tool_name,
|
|||
|
|
"match_score": 0.0,
|
|||
|
|
"scenarios": [],
|
|||
|
|
"complexity": "unknown",
|
|||
|
|
"adapter_type": "unknown",
|
|||
|
|
"integration_plan": "",
|
|||
|
|
"risks": [],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
response = llm_service.chat_sync(
|
|||
|
|
messages=[{"role": "user", "content": prompt}],
|
|||
|
|
temperature=0.3,
|
|||
|
|
max_tokens=1000,
|
|||
|
|
)
|
|||
|
|
content = response.get("content", "") if isinstance(response, dict) else str(response)
|
|||
|
|
# 提取 JSON
|
|||
|
|
json_match = self._extract_json(content)
|
|||
|
|
if json_match:
|
|||
|
|
evaluation = json.loads(json_match)
|
|||
|
|
result.update(evaluation)
|
|||
|
|
result["llm_raw"] = content[:500]
|
|||
|
|
except Exception as e:
|
|||
|
|
logger.error("LLM 评估工具 %s 失败: %s", tool_name, e)
|
|||
|
|
result["error"] = str(e)
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def _extract_json(self, text: str) -> Optional[str]:
|
|||
|
|
"""从文本中提取 JSON 块。"""
|
|||
|
|
import re
|
|||
|
|
# 尝试提取 ```json ... ``` 块
|
|||
|
|
match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
|
|||
|
|
if match:
|
|||
|
|
return match.group(1)
|
|||
|
|
# 尝试直接找 {...}
|
|||
|
|
match = re.search(r'\{[^{}]*"match_score"[^{}]*\}', text, re.DOTALL)
|
|||
|
|
if match:
|
|||
|
|
return match.group(0)
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def discover_and_rank(self, limit: int = 10) -> Dict[str, Any]:
|
|||
|
|
"""完整的发现+评估+排序流程。"""
|
|||
|
|
internal = self.scan_internal_tools()
|
|||
|
|
|
|||
|
|
# 聚焦未注册的工具
|
|||
|
|
unregistered = [t for t in internal if not t["registered"]]
|
|||
|
|
|
|||
|
|
evaluations = []
|
|||
|
|
for tool in unregistered[:limit]:
|
|||
|
|
eval_result = self.evaluate_tool(
|
|||
|
|
tool_name=tool["tool_name"],
|
|||
|
|
tool_source="internal",
|
|||
|
|
)
|
|||
|
|
evaluations.append(eval_result)
|
|||
|
|
|
|||
|
|
evaluations.sort(key=lambda x: x.get("match_score", 0), reverse=True)
|
|||
|
|
|
|||
|
|
high_match = [e for e in evaluations if e.get("match_score", 0) >= 0.8]
|
|||
|
|
medium_match = [e for e in evaluations if 0.5 <= e.get("match_score", 0) < 0.8]
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"total_discovered": len(internal),
|
|||
|
|
"unregistered": len(unregistered),
|
|||
|
|
"evaluated": len(evaluations),
|
|||
|
|
"high_match": high_match,
|
|||
|
|
"medium_match": medium_match,
|
|||
|
|
"all_ranked": evaluations,
|
|||
|
|
"recommendation": (
|
|||
|
|
f"发现 {len(high_match)} 个高匹配工具建议立即集成, "
|
|||
|
|
f"{len(medium_match)} 个中匹配工具可进一步评估"
|
|||
|
|
),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def generate_adapter(self, tool_name: str, evaluation: Dict[str, Any]) -> str:
|
|||
|
|
"""根据评估结果生成 tool adapter 代码框架。"""
|
|||
|
|
adapter_type = evaluation.get("adapter_type", "api_wrapper")
|
|||
|
|
|
|||
|
|
if adapter_type == "api_wrapper":
|
|||
|
|
return self._generate_api_adapter(tool_name, evaluation)
|
|||
|
|
elif adapter_type == "code_executor":
|
|||
|
|
return self._generate_code_adapter(tool_name, evaluation)
|
|||
|
|
else:
|
|||
|
|
return self._generate_plugin_adapter(tool_name, evaluation)
|
|||
|
|
|
|||
|
|
def _generate_api_adapter(self, tool_name: str, eval_result: Dict[str, Any]) -> str:
|
|||
|
|
"""生成 API 包装器 adapter。"""
|
|||
|
|
return f'''"""
|
|||
|
|
{tool_name} — 自动发现的工具适配器
|
|||
|
|
匹配度: {eval_result.get("match_score", "N/A")}
|
|||
|
|
场景: {", ".join(eval_result.get("scenarios", []))}
|
|||
|
|
"""
|
|||
|
|
from typing import Any, Dict, Optional
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def {tool_name}(params: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
"""Auto-generated adapter for {tool_name}"""
|
|||
|
|
try:
|
|||
|
|
# TODO: 根据 API 文档实现具体调用逻辑
|
|||
|
|
result = {{
|
|||
|
|
"success": True,
|
|||
|
|
"data": None,
|
|||
|
|
"message": "Adapter stub — 请根据 API 文档完善",
|
|||
|
|
}}
|
|||
|
|
return result
|
|||
|
|
except Exception as e:
|
|||
|
|
return {{"success": False, "error": str(e)}}
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
def _generate_code_adapter(self, tool_name: str, eval_result: Dict[str, Any]) -> str:
|
|||
|
|
return f'''"""
|
|||
|
|
{tool_name} — 代码执行器适配器
|
|||
|
|
匹配度: {eval_result.get("match_score", "N/A")}
|
|||
|
|
"""
|
|||
|
|
import subprocess
|
|||
|
|
from typing import Any, Dict
|
|||
|
|
|
|||
|
|
|
|||
|
|
async def {tool_name}(code: str, language: str = "python") -> Dict[str, Any]:
|
|||
|
|
"""Auto-generated code executor adapter for {tool_name}"""
|
|||
|
|
try:
|
|||
|
|
executor = "python" if language == "python" else "node"
|
|||
|
|
result = subprocess.run(
|
|||
|
|
[executor, "-c", code],
|
|||
|
|
capture_output=True, text=True, timeout=30
|
|||
|
|
)
|
|||
|
|
return {{
|
|||
|
|
"success": result.returncode == 0,
|
|||
|
|
"stdout": result.stdout,
|
|||
|
|
"stderr": result.stderr,
|
|||
|
|
}}
|
|||
|
|
except Exception as e:
|
|||
|
|
return {{"success": False, "error": str(e)}}
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
def _generate_plugin_adapter(self, tool_name: str, eval_result: Dict[str, Any]) -> str:
|
|||
|
|
return f'''"""
|
|||
|
|
{tool_name} — 插件适配器
|
|||
|
|
匹配度: {eval_result.get("match_score", "N/A")}
|
|||
|
|
"""
|
|||
|
|
from typing import Any, Dict
|
|||
|
|
|
|||
|
|
|
|||
|
|
class {tool_name.title().replace("_", "")}Plugin:
|
|||
|
|
"""Auto-generated plugin adapter for {tool_name}"""
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.name = "{tool_name}"
|
|||
|
|
|
|||
|
|
async def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
|
"""执行插件逻辑"""
|
|||
|
|
# TODO: 根据文档实现
|
|||
|
|
return {{"success": True, "data": None}}
|
|||
|
|
'''
|
|||
|
|
|
|||
|
|
|
|||
|
|
tool_discovery = ToolDiscovery()
|