RAG 进阶:Agentic RAG 与多智能体协作(系列二·第1篇):高级工具设计

让 Agent 不只是"查资料",而是能真正"操作世界"

系列一解决了"怎么搭一个 Agent",系列二从工具开始深入。工具是 Agent 的手和脚------没有好工具,Agent 再聪明也只是个聊天机器人。

这篇讲三类最实用的高级工具:代码解释器、数据库查询、工作流触发,重点放在"怎么安全地用"。


一、代码解释器:让 Agent 会算、会画图、会处理文件

很多 Agent 框架直接用 exec() 跑 LLM 生成的代码,这是极度危险 的。LLM 生成的代码可能包含 os.system("rm -rf /")、网络请求、无限循环。

安全做法:沙箱隔离 + 白名单限制。

方案 A:用 E2B 沙箱(推荐)

E2B 提供隔离的云端代码执行环境,自带资源限制和网络隔离。

python 复制代码
from e2b_code_interpreter import Sandbox

def run_code_in_sandbox(code: str, timeout: int = 30) -> dict:
    """在沙箱中执行 Python 代码"""
    sandbox = Sandbox()
    try:
        result = sandbox.run_code(code, timeout=timeout)
        return {
            "stdout": result.logs.stdout,
            "stderr": result.logs.stderr,
            "files": sandbox.files.list("/"),  # 可以读取生成的文件
        }
    finally:
        sandbox.kill()

# Agent 工具定义
code_interpreter_tool = {
    "name": "code_interpreter",
    "description": "执行 Python 代码进行计算、数据处理、绘图。支持 matplotlib、pandas、numpy。",
    "parameters": {
        "type": "object",
        "properties": {
            "code": {
                "type": "string",
                "description": "要执行的 Python 代码"
            }
        },
        "required": ["code"]
    }
}

方案 B:本地轻量沙箱(适合离线场景)

python 复制代码
import subprocess
import tempfile
import os

SAFE_MODULES = {"math", "statistics", "decimal", "fractions", "json", "csv"}
DANGEROUS_PATTERNS = [
    "import os", "import sys", "import subprocess", "import socket",
    "import requests", "import urllib", "__import__", "exec(", "eval(",
    "open(", "input(", "compile(", "globals(", "locals("
]

def safe_code_execute(code: str) -> dict:
    """本地安全代码执行:模块白名单 + 危险模式检测"""
    # 1. 危险模式检测
    for pattern in DANGEROUS_PATTERNS:
        if pattern in code:
            return {"error": f"禁止使用危险操作: {pattern}"}
    
    # 2. 写入临时文件
    with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
        f.write(code)
        tmp_path = f.name
    
    try:
        # 3. 在受限环境中执行(无网络、超时限制)
        result = subprocess.run(
            ["python3", tmp_path],
            capture_output=True,
            text=True,
            timeout=10,
            env={**os.environ, "PYTHONPATH": ""}  # 清空 PYTHONPATH
        )
        return {
            "stdout": result.stdout[:5000],  # 限制输出长度
            "stderr": result.stderr[:2000],
            "returncode": result.returncode
        }
    except subprocess.TimeoutExpired:
        return {"error": "代码执行超时"}
    finally:
        os.unlink(tmp_path)

典型应用场景:

  • "帮我算一下这个数据集的平均值和标准差"
  • "把这份 CSV 数据画成折线图"
  • "把下面这段 JSON 转成表格格式"

二、数据库查询:让 Agent 会查数据

核心原则:Agent 只读不写,且查询必须经过 SQL 注入防护。

python 复制代码
import sqlite3
import re

class SafeDBQuery:
    """安全的数据库查询工具"""
    
    def __init__(self, db_path: str):
        self.db_path = db_path
    
    def execute_query(self, sql: str) -> list[dict]:
        # 1. 只允许 SELECT
        sql_upper = sql.strip().upper()
        if not sql_upper.startswith("SELECT"):
            raise ValueError("只允许 SELECT 查询")
        
        # 2. 禁止危险操作
        dangerous = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "CREATE"]
        for keyword in dangerous:
            if keyword in sql_upper:
                raise ValueError(f"禁止使用 {keyword} 操作")
        
        # 3. 限制返回行数
        if "LIMIT" not in sql_upper:
            sql = sql.rstrip(";") + " LIMIT 100"
        
        # 4. 执行查询
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            cursor = conn.execute(sql)
            rows = [dict(row) for row in cursor.fetchall()]
            return rows
        finally:
            conn.close()

# 工具定义
db_tool = {
    "name": "query_database",
    "description": "查询业务数据库。只支持 SELECT,自动限制 100 行。",
    "parameters": {
        "type": "object",
        "properties": {
            "sql": {
                "type": "string",
                "description": "SQL 查询语句(仅 SELECT)"
            }
        },
        "required": ["sql"]
    }
}

# 使用示例
db = SafeDBQuery("business.db")
result = db.execute_query("SELECT name, revenue FROM companies ORDER BY revenue DESC")

进阶技巧 :给 Agent 提供表结构描述而不是让它猜字段名。

ini 复制代码
SCHEMA_DESCRIPTION = """
数据库表结构:
- companies: id, name, industry, revenue, employee_count, founded_year
- orders: id, company_id, amount, order_date, status
- products: id, name, category, price, stock
"""

# 在 System Prompt 中注入
system_prompt = f"""你是一个数据分析助手。{SCHEMA_DESCRIPTION}
查询时请使用上述字段名。"""

三、工作流触发:让 Agent 能"启动一个流程"

Agent 不只是回答问题,还可以触发业务流程。关键是参数校验 和异步执行。

python 复制代码
import asyncio
from enum import Enum

class WorkflowType(str, Enum):
    CREATE_TICKET = "create_ticket"
    SEND_EMAIL = "send_email"
    GENERATE_REPORT = "generate_report"
    APPROVE_REQUEST = "approve_request"

# 工作流注册表
WORKFLOW_REGISTRY = {
    WorkflowType.CREATE_TICKET: {
        "description": "创建工单",
        "params": ["title", "description", "priority", "assignee"],
        "handler": "ticket_service.create",
    },
    WorkflowType.SEND_EMAIL: {
        "description": "发送邮件",
        "params": ["to", "subject", "body"],
        "handler": "email_service.send",
    },
}

def trigger_workflow(workflow_type: str, params: dict) -> dict:
    """触发一个工作流"""
    # 1. 校验工作流类型
    if workflow_type not in WORKFLOW_REGISTRY:
        return {"error": f"未知工作流: {workflow_type}"}
    
    # 2. 校验必填参数
    required = WORKFLOW_REGISTRY[WorkflowType(workflow_type)]["params"]
    missing = [p for p in required if p not in params]
    if missing:
        return {"error": f"缺少参数: {', '.join(missing)}"}
    
    # 3. 异步执行(不阻塞 Agent)
    handler_path = WORKFLOW_REGISTRY[WorkflowType(workflow_type)]["handler"]
    # 实际项目中通过消息队列或 Celery 异步执行
    task_id = f"{workflow_type}_{id(params)}"
    
    return {
        "status": "accepted",
        "task_id": task_id,
        "message": f"工作流 {workflow_type} 已触发,任务ID: {task_id}"
    }

# 工具定义
workflow_tool = {
    "name": "trigger_workflow",
    "description": "触发一个业务流程。可用类型: create_ticket, send_email, generate_report, approve_request",
    "parameters": {
        "type": "object",
        "properties": {
            "workflow_type": {
                "type": "string",
                "enum": list(WorkflowType),
                "description": "工作流类型"
            },
            "params": {
                "type": "object",
                "description": "工作流参数"
            }
        },
        "required": ["workflow_type", "params"]
    }
}

Agent 使用示例:

csharp 复制代码
用户:"客户投诉服务器宕机,优先级很高,派给运维组"
Agent 思考:需要创建工单 → 调用 trigger_workflow
  workflow_type: "create_ticket"
  params: {
    "title": "客户投诉:服务器宕机",
    "description": "客户反映服务器宕机,需紧急处理",
    "priority": "high",
    "assignee": "ops_team"
  }

四、工具设计的安全清单

风险 防护措施
代码注入 沙箱隔离 / 白名单模块 / 危险模式检测
SQL 注入 只允许 SELECT / 参数化查询 / 表结构白名单
工具滥用 频率限制 / 配额管理 / 人工审批节点
数据泄露 脱敏中间结果 / 不返回完整记录 / 审计日志
无限循环 超时限制 / 最大步数限制 / 死循环检测
资源耗尽 内存限制 / CPU 限制 / 输出长度限制

相关推荐
杨超越luckly1 小时前
国庆V型、春节迁徙、五一升温:节假日数据里的中国旅游
agent·旅游·数据可视化·节假日·cpi·国定假日
熊猫钓鱼>_>1 小时前
当工具成为类:Softmax困境与连接器过载的算法根源
算法·ai·agent·工具·曲线·华为云码道·生态图鉴
云半S一1 小时前
Agent 学习总结一
经验分享·笔记·学习·agent
桃桃乌龙_2 小时前
从 ChatGPT 到 Agent Harness:近五年 AI 技术演进全景(MCP / Skills / A2A 详解)
人工智能·agent
Topskys2 小时前
RAG 准确率提升体系化方案
agent
ADark2 小时前
FDE 入门 · 11|数据那一摊:项目卡死的地方通常在这儿
人工智能·agent
武子康2 小时前
AI Agent 设备指令超时后,重试会执行两次吗?
人工智能·后端·agent
程序猿DD2 小时前
有了这个无限画布,我也能做漫画了!
agent
用户522704833582 小时前
Harness踩坑笔记01---外部搜索工具返回结果的解码问题
agent