agent教程S02 工具路由分发

S02 工具路由分发|结构化整理教程(提出问题→分析问题→解决问题|承接S01最小Agent循环,新手友好)

前置铺垫:S01完成Agent基础闭环:用户提问→LLM→工具执行→结果回填消息→新一轮推理;S02核心目标:解耦工具与主循环、新增工具不用修改agent_loop代码、给文件操作增加安全隔离,新增工具分发层,落地4类常用工具:bash、read_file、write_file、edit_file。

一、提出问题:S01硬编码工具带来3个致命痛点

痛点1:工具变多后if/elif代码无限膨胀,维护困难

S01工具执行靠if/elif判断工具名称,只有单个计算器工具没问题;一旦扩展读写文件、shell等多个工具,run_tool函数会堆满条件分支,每新增一个工具就必须修改工具判断代码,代码臃肿难迭代。

痛点2:纯bash操作无路径管控,存在高危安全漏洞

早期全部依赖Shell命令操作文件,没有访问限制:模型可通过指令读取系统隐私文件(cat /etc/passwdC:\Windows\system32);sed/cat遇到特殊字符容易命令报错、程序崩溃,缺少安全沙箱隔离。

痛点3:新增工具需要改动Agent主循环,破坏闭环稳定性

工具执行逻辑和agent_loop主循环代码深度绑定,想要拓展功能就要修改循环本体,S01定型的Agent闭环被频繁改动,极易引入循环逻辑bug。

项目诉求 :搭建分层架构,做到:主循环代码永久固定不动;新增工具仅需注册对应函数;限制文件只能在指定目录访问

二、分析问题:根源拆解+核心概念通俗解析

1、问题根源

S01没有分层设计,工具路由分发逻辑与Agent主循环代码耦合,缺少独立中间调度层,工具的新增、修改会连带改动主流程代码。

2、标准分层架构(S02设计核心)

复制代码
用户输入 → LLM(根据TOOLS工具说明书选择工具+生成参数) → 【Dispatch工具分发层(字典映射)】 → 独立工具处理函数 → tool_result结果 → 写入messages → 下一轮LLM

S02黄金准则:新增一个工具,只新增1个handler函数 + 补充工具schema,Agent主循环一行代码不用改

3、2个必背核心名词

  1. TOOL_HANDLERS(分发映射字典) :代码路由表,key=工具名称(和TOOLS内name严格一致)、value=工具执行函数,用字典查表彻底替换if/elif判断;
  2. safe_path(路径沙箱) :安全校验函数,强制所有文件操作路径必须落在自定义工作目录WORKDIR内部,路径越界直接抛出异常,杜绝跨目录窃取文件。

4、S01 vs S02 组件变更对照表

组件 S01(旧版硬编码) S02(新版分发架构)
可用工具 单一工具(计算器/bash) 4个标准化工具:bash/read_file/write_file/edit_file
工具分发 if/elif逐个判断 TOOL_HANDLERS字典查表分发
文件安全 无任何路径限制 safe_path沙箱路径校验
Agent主循环 循环代码 完全复用S01,零修改

5、进阶隐藏问题:原生messages不能直接发给API

项目内部messages会附带自定义元数据(_internal/_timestamp/_source),直接传给DeepSeek接口会返回400报错;

API强制3条协议规则:

① 每一条tool_use调用,必须配套一条tool_result结果(通过tool_use_id绑定);

user/assistant消息必须交替出现,不能连续同角色;

③ 只保留接口协议规定字段,剔除项目内部自定义字段。

→ 解决方案:新增normalize_messages消息规范化函数,发送API前清洗消息。

三、解决问题:分模块落地代码(兼容DeepSeek API,完全沿用S01主循环)

模块1:定义工作目录+safe_path安全沙箱(解决文件越权访问)

python 复制代码
from pathlib import Path
# 全局限定:所有文件只能操作agent_workspace文件夹
WORKDIR = Path("./agent_workspace")
WORKDIR.mkdir(exist_ok=True)

def safe_path(p: str) -> Path:
    full_path = (WORKDIR / p).resolve()
    # 路径不在工作目录内,直接禁止访问
    if not full_path.is_relative_to(WORKDIR):
        raise ValueError(f"非法路径:{p},禁止访问工作目录外文件")
    return full_path

模块2:编写4个独立工具Handler(read/write/edit/bash)

每个工具单独封装逻辑,所有文件操作强制先走safe_path校验路径:

python 复制代码
import subprocess
# 读取文件,limit可选限制读取行数
def run_read(path: str, limit: int = None) -> str:
    fp = safe_path(path)
    text = fp.read_text(encoding="utf-8")
    lines = text.splitlines()
    if limit and len(lines) > limit:
        lines = lines[:limit]
    return "\n".join(lines)[:50000] # 限制超长返回

# 写入文件
def run_write(path: str, content: str) -> str:
    fp = safe_path(path)
    fp.write_text(content, encoding="utf-8")
    return f"成功写入文件:{path}"

# 替换文本编辑文件
def run_edit(path: str, old_text: str, new_text: str) -> str:
    fp = safe_path(path)
    raw = fp.read_text(encoding="utf-8")
    new_content = raw.replace(old_text, new_text)
    fp.write_text(new_content, encoding="utf-8")
    return f"{path} 文件内容替换完成"

# 执行shell终端命令
def run_bash(command: str) -> str:
    try:
        res = subprocess.check_output(command, shell=True, encoding="utf-8", stderr=subprocess.STDOUT)
        return res
    except Exception as e:
        return f"命令执行失败:{str(e)}"

模块3:TOOL_HANDLERS分发字典(S02最核心,替代所有if判断)

python 复制代码
# key和TOOLS里工具name完全一致,新增工具只在这里追加一行
TOOL_HANDLERS = {
    "bash": lambda **kw: run_bash(kw["command"]),
    "read_file": lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"])
}

模块4:Agent主循环(整体复用S01,仅修改工具执行片段)

agent_loopwhile循环、消息追加、模型请求逻辑全部照搬S01,只把原if判断替换为字典查表:

python 复制代码
# 循环内工具执行核心代码(仅此处改动)
import json
tool_returns = []
for tool_call in msg.tool_calls:
    args = json.loads(tool_call.function.arguments) # 解析模型返回的参数JSON字符串
    handler = TOOL_HANDLERS.get(tool_call.function.name) # 查表找函数
    if handler:
        output = handler(**args)
    else:
        output = f"未知工具:{tool_call.function.name}"
    tool_returns.append({"role": "tool", "tool_call_id": tool_call.id, "content": output})
state["messages"].extend(tool_returns)
state["turn_count"] += 1

模块5:TOOLS配置(给DeepSeek的工具说明书,同S01规则)

TOOLS列表用来告诉模型可用工具,function.name必须和TOOL_HANDLERS字典key一一对应,示例:

python 复制代码
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": "读取工作目录下指定txt文件内容,path为相对路径,limit可选限制读取行数",
            "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "limit": {"type": "integer"}}, "required": ["path"]}
        }
    },
    # 依次补充 write_file / edit_file / bash 三个工具配置
]

模块6:进阶:消息规范化 normalize_messages(新手初学可暂时跳过)

每次调用DeepSeek API前,调用该函数清洗messages,生成接口合规的消息副本,原始内部messages保持不变

python 复制代码
# 调用模型时使用规范化消息
resp = client.chat.completions.create(
    model=MODEL,
    messages=normalize_messages(state["messages"]),
    tools=TOOLS
)

初学优先跑通分发+沙箱,规范化放到后期复杂项目再落地。

四、新手实战测试(4组实操指令)

  1. 运行脚本:python s02_tool_use.py
  2. 4条测试Prompt(英文指令模型识别效果更好)
    1)Create a file called greet.py with a greet(name) function → write_file新建代码文件
    2)Edit greet.py to add docstring for greet function → edit_file编辑注释
    3)Read greet.py to view full source code → read_file读取内容
    4)ls ./agent_workspace → bash查看目录文件

五、拓展练习与后续学习

  1. 入门练手 :在当前架构新增计算器工具,仅需三步:①写run_calc计算函数;②TOOL_HANDLERS字典新增一行;③TOOLS补充计算器描述,全程不用修改agent_loop主循环,直观感受解耦优势;
  2. 进阶学习s02a-tool-control-plane.md工具控制平面,后续可拓展:工具权限、文件缓存、MCP客户端、调用日志,属于框架升级内容,初学不用深究。

六、S02一句话总结

依托TOOL_HANDLERS字典实现工具解耦分发,依托safe_path实现文件访问安全;新增工具只补handler和工具描述,Agent主循环永久不用改动

S02 工具路由分发|完整可运行实战代码(DeepSeek API 兼容版)

核心特性 :安全沙箱 + 工具字典分发 + 4大实用工具(读/写/编辑文件 + Bash)+ 零修改主循环

一键运行:替换 API Key 即可直接执行,无需额外配置

python 复制代码
from openai import OpenAI
import json
from pathlib import Path
import subprocess

# ===================== 1. 全局配置(必填你的DeepSeek Key)=====================
DEEPSEEK_API_KEY = "你的DeepSeek API Key"
client = OpenAI(
    api_key=DEEPSEEK_API_KEY,
    base_url="https://api.deepseek.com"
)
MODEL = "deepseek-chat"

# 系统提示词:强制模型使用工具,禁止编造结果
SYSTEM_PROMPT = """
你是专业的AI智能体,必须严格调用工具完成任务:
1. 文件操作只能用 read_file/write_file/edit_file,禁止手写内容
2. 终端命令只能用 bash 执行
3. 拿到工具结果后再输出最终答案,禁止凭空回答
"""

# ===================== 2. 安全沙箱(核心:限制文件只能在指定目录操作)=====================
# 所有文件只能操作此文件夹,杜绝系统文件风险
WORKDIR = Path("./agent_workspace")
WORKDIR.mkdir(exist_ok=True)  # 自动创建目录

def safe_path(file_path: str) -> Path:
    """安全路径校验:禁止访问工作目录外的文件"""
    full_path = (WORKDIR / file_path).resolve()
    if not full_path.is_relative_to(WORKDIR):
        raise PermissionError(f"非法路径!仅允许操作 {WORKDIR} 内的文件")
    return full_path

# ===================== 3. 4个核心工具函数(解耦独立,新增工具只加这里)=====================
def run_bash(command: str) -> str:
    """执行终端Bash命令"""
    try:
        result = subprocess.check_output(
            command, shell=True, encoding="utf-8",
            stderr=subprocess.STDOUT, timeout=10
        )
        return f"命令执行成功:\n{result}"
    except Exception as e:
        return f"命令执行失败:{str(e)}"

def run_read_file(path: str, limit: int = None) -> str:
    """读取文件内容,limit限制行数"""
    try:
        file_path = safe_path(path)
        content = file_path.read_text(encoding="utf-8")
        if limit:
            lines = content.splitlines()[:limit]
            content = "\n".join(lines)
        return f"文件 {path} 内容:\n{content}"
    except Exception as e:
        return f"读取文件失败:{str(e)}"

def run_write_file(path: str, content: str) -> str:
    """写入文件(覆盖写入)"""
    try:
        file_path = safe_path(path)
        file_path.write_text(content, encoding="utf-8")
        return f"✅ 成功写入文件:{path}"
    except Exception as e:
        return f"写入文件失败:{str(e)}"

def run_edit_file(path: str, old_text: str, new_text: str) -> str:
    """替换文本编辑文件"""
    try:
        file_path = safe_path(path)
        original = file_path.read_text(encoding="utf-8")
        updated = original.replace(old_text, new_text)
        file_path.write_text(updated, encoding="utf-8")
        return f"✅ 成功编辑文件:{path}"
    except Exception as e:
        return f"编辑文件失败:{str(e)}"

# ===================== 4. 工具路由字典(S02核心!替代if/elif,查表分发)=====================
# 格式:工具名(和TOOLS一致) → 对应执行函数
TOOL_HANDLERS = {
    "bash": run_bash,
    "read_file": run_read_file,
    "write_file": run_write_file,
    "edit_file": run_edit_file
}

# ===================== 5. 工具说明书(告诉模型有哪些工具可用)=====================
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "bash",
            "description": "执行终端命令,支持ls、dir等基础指令",
            "parameters": {
                "type": "object",
                "properties": {"command": {"type": "string", "description": "要执行的命令"}},
                "required": ["command"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": "读取工作目录内的文件",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径,如 test.txt"},
                    "limit": {"type": "integer", "description": "限制读取行数,可选"}
                },
                "required": ["path"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "write_file",
            "description": "写入文本到文件",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "content": {"type": "string", "description": "要写入的内容"}
                },
                "required": ["path", "content"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "edit_file",
            "description": "替换文件内的文本",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": "文件路径"},
                    "old_text": {"type": "string", "description": "要替换的旧内容"},
                    "new_text": {"type": "string", "description": "新内容"}
                },
                "required": ["path", "old_text", "new_text"]
            }
        }
    }
]

# ===================== 6. Agent主循环(完全复用S01,零修改!)=====================
def agent_loop(user_input):
    # 初始化状态:记忆 + 轮次
    state = {
        "messages": [{"role": "user", "content": user_input}],
        "turn_count": 1
    }

    while True:
        print(f"\n=== 第 {state['turn_count']} 轮推理 ===")
        
        # 调用DeepSeek API
        resp = client.chat.completions.create(
            model=MODEL,
            messages=[{"role": "system", "content": SYSTEM_PROMPT}] + state["messages"],
            tools=TOOLS
        )

        # 获取模型回复
        msg = resp.choices[0].message
        state["messages"].append(msg.model_dump(exclude_none=True))

        # 无工具调用 → 输出答案,结束循环
        if not msg.tool_calls:
            print("\n🎉 最终回答:", msg.content)
            return

        # ===================== 工具分发执行(S02核心逻辑)=====================
        tool_results = []
        for tool_call in msg.tool_calls:
            # 解析模型返回的参数
            func = tool_call.function
            args = json.loads(func.arguments)
            
            # 查表找到对应工具函数并执行
            handler = TOOL_HANDLERS.get(func.name)
            if handler:
                result = handler(**args)
            else:
                result = f"❌ 未知工具:{func.name}"

            # 组装工具结果,回传给模型
            tool_results.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

        # 工具结果写入记忆,进入下一轮
        state["messages"].extend(tool_results)
        state["turn_count"] += 1

# ===================== 7. 实战测试(一键运行4个工具)=====================
if __name__ == "__main__":
    # 测试1:写入文件
    agent_loop("创建一个文件叫 note.txt,内容写:S02工具路由分发学习成功")
    
    # 测试2:读取文件
    agent_loop("读取 note.txt 文件的内容")
    
    # 测试3:编辑文件
    agent_loop("把 note.txt 里的内容替换为:S02工具路由分发+安全沙箱双完成")
    
    # 测试4:Bash命令查看目录
    agent_loop("执行命令查看 agent_workspace 文件夹里的文件")

🚀 运行说明(初学者必看)

  1. 替换密钥 :把代码里 你的DeepSeek API Key 换成你自己的密钥
  2. 安装依赖pip install openai
  3. 直接运行 :执行代码,会自动完成:
    • 创建 agent_workspace 安全目录
    • 自动调用4个工具(写/读/编辑文件 + Bash)
    • 打印每一轮推理过程 + 最终答案

✅ 核心验证效果

你会看到控制台输出:

  1. 第1轮:模型调用 write_file 写入笔记
  2. 第2轮:模型调用 read_file 读取内容
  3. 第3轮:模型调用 edit_file 修改内容
  4. 第4轮:模型调用 bash 查看文件列表

🎯 S02 核心亮点(一眼看懂)

  1. 无if/elif :用 TOOL_HANDLERS 字典查表分发工具
  2. 主循环零修改:新增工具只加函数 + 注册字典,不用动循环代码
  3. 安全沙箱 :所有文件强制在 agent_workspace 内操作,绝对安全
  4. 标准化工具:4个高频工具开箱即用,可无限扩展
相关推荐
wangqiaowq1 小时前
“Alpaca JSON” 通常指的是大语言模型(LLM)微调领域中的一种指令微调数据集格式。
人工智能
garmin Chen1 小时前
Prompt工程入门:让AI按你的要求工作(1)--prompt概述与设计
java·人工智能·python·junit·prompt·agent
流放深圳1 小时前
抓住 AI 人工智能的风口之第 5 章 —— 使用视觉大模型(Vision-Language Model)支持图片识别,完善电商智能客服项目
人工智能·视觉大模型·图片识别·springai·vision-language
imDwAaY1 小时前
从感知机到 Attention:我用 PyTorch 打穿 CS188 机器学习终章 CS188 Proj5 学习笔记
人工智能·pytorch·笔记·python·学习·机器学习
龙萱坤诺2 小时前
无限画布 + gpt-image-2:用智狐AI工作台把AI草图直接拖进排版区
人工智能·ai短剧·无限画布
马***41110 小时前
适配成人英语学习痛点,打造落地性强的学习辅助方式
人工智能·学习
夜焱辰10 小时前
浏览器端 Agent 的文件版本管理:不用 Git,基于 OPFS + SQLite 自己造了一个
前端·人工智能
Ricky055310 小时前
CTRL-WORLD:一种用于机器人操控的可控生成世界模型(中美2025年联合研究)
人工智能·机器人·世界模型
jeffer_liu10 小时前
Spring AI 生产级实战:工具调用
java·人工智能·后端·spring·ai编程