Malize's blog Malize's blog
首页
  • 设计模式

    • 设计模式总览
    • 工作中用到的设计模式
  • 并发编程

    • 死锁
  • 技术文档

    • Docker 核心命令大全
    • Markdown 使用教程
    • npm 常用命令
    • yaml 语言教程
    • Nodejs 递归读文件
  • 构造问答系统

    • 项目背景
    • 构建答疑机器人
    • 扩展知识范围
    • 优化提示词
    • 自动化评测
    • 优化 RAG 应用
  • 构建 Agent 系统

    • Agent 基础与工具调用
    • 规划与执行
    • 多 Agent 团队协作
    • Memory 积累经验
    • Skill 可复用流程
    • Qwen Code 实践
  • 交付上线

    • 走向生产环境
    • 模型蒸馏
    • 部署模型
    • 生产实践
    • 安全合规
  • 工具手册

    • OpenClaw 命令速查
  • 规范 & 实践

    • 代码规范
    • sharding-jdbc
    • CIM 半导体行业
    • HTML 常用 meta
    • CSS 技巧收藏
  • 微服务

    • feign原理
  • Git

    • Git 笔记总览
    • Git 使用手册
    • Git 修改分支名
    • 团队 Git 分支规范
  • GitHub & 博客

    • GitHub 高级搜索技巧
    • GitHub Actions 自动部署
    • 博客搭建 - 百度收录
  • 优质网站
  • 前端库推荐
  • 成长学习

    • 学习方法
    • 敏捷开发实战
    • 提示词工程
  • 生活

    • 实用技巧
    • 心情杂货
    • 梦境与灵感
  • 技术问题

    • 面试问题备忘
  • 索引

    • 分类
    • 标签
    • 按年归档
GitHub (opens new window)

Malize

持续学习,持续成长
首页
  • 设计模式

    • 设计模式总览
    • 工作中用到的设计模式
  • 并发编程

    • 死锁
  • 技术文档

    • Docker 核心命令大全
    • Markdown 使用教程
    • npm 常用命令
    • yaml 语言教程
    • Nodejs 递归读文件
  • 构造问答系统

    • 项目背景
    • 构建答疑机器人
    • 扩展知识范围
    • 优化提示词
    • 自动化评测
    • 优化 RAG 应用
  • 构建 Agent 系统

    • Agent 基础与工具调用
    • 规划与执行
    • 多 Agent 团队协作
    • Memory 积累经验
    • Skill 可复用流程
    • Qwen Code 实践
  • 交付上线

    • 走向生产环境
    • 模型蒸馏
    • 部署模型
    • 生产实践
    • 安全合规
  • 工具手册

    • OpenClaw 命令速查
  • 规范 & 实践

    • 代码规范
    • sharding-jdbc
    • CIM 半导体行业
    • HTML 常用 meta
    • CSS 技巧收藏
  • 微服务

    • feign原理
  • Git

    • Git 笔记总览
    • Git 使用手册
    • Git 修改分支名
    • 团队 Git 分支规范
  • GitHub & 博客

    • GitHub 高级搜索技巧
    • GitHub Actions 自动部署
    • 博客搭建 - 百度收录
  • 优质网站
  • 前端库推荐
  • 成长学习

    • 学习方法
    • 敏捷开发实战
    • 提示词工程
  • 生活

    • 实用技巧
    • 心情杂货
    • 梦境与灵感
  • 技术问题

    • 面试问题备忘
  • 索引

    • 分类
    • 标签
    • 按年归档
GitHub (opens new window)
  • 阿里云大模型ACP整理

    • 课程准备

    • 构造问答系统

    • 构建Agent系统

      • 从回答问题到解决问题
      • Agent 基础与工具调用
        • 这篇讲什么
        • 1 第一个工具函数:最简单的实现
        • 2 意图识别:让 Agent 决定用什么工具
          • 2.1 关键词匹配:脆弱的方案
          • 2.2 用大模型来做意图识别
        • 3 结构化输出:让工具调用可靠
        • 4 Function Calling:行业标准
        • 5 MCP 协议:工具的规模化管理
          • 5.1 问题:Schema 硬编码
          • 5.2 MCP 的解耦思想
          • 5.3 用 MCP 重构 web_search
        • 个人总结
      • 让 Agent 学会规划与执行
      • 用多 Agent 实现团队协作
      • 用 Memory 让 Agent 积累经验
      • 用 Skill 将能力固化为可复用流程
      • 用评测驱动 Agent 开发
      • Qwen Code 实践
    • 交付上线

    • 总结与展望

    • 工具手册

  • 大模型
  • 阿里云大模型ACP整理
  • 构建Agent系统
malize
2026-02-02
目录

Agent 基础与工具调用

# Agent 基础与工具调用

答疑机器人能回答员工手册里的问题了,但知识库之外的事情它无能为力——不能搜互联网、也不能查数据库。这不是 RAG 的问题,而是大模型的本质限制:它只能基于已有文本对话,无法主动与外部环境交互。这节课我主要搞清楚了 Agent 工具调用从零到工业标准的完整链路。

# 这篇讲什么

  • Agent 为什么能"动手":工具定义 → 工具选择 → 结构化输出 → 工具执行四步链路
  • Function Calling 的完整机制与 JSON Schema 设计(核心重点)
  • ReAct 框架:思考-行动-观察循环
  • MCP 协议:为什么要解耦工具定义和使用

环境初始化略,见课程仓库。我用阿里云 PAI-DSW 跑的代码,CPU 实例就够,新用户有 3 个月免费额度。

# 1 第一个工具函数:最简单的实现

让机器人能联网搜索,最直接的思路是:写一个 web_search 函数,每次都调用它,把结果和问题一起发给大模型。

# 1. 用户的原始请求
user_request = "你好,请帮我搜集一些关于 Transformer 模型的最新资料。"

# 2. "硬编码"执行工具函数
def web_search(query: str):
    """模拟执行网络搜索并返回JSON格式的结果"""
    print(f"--- [工具执行中] 正在搜索: {query} ---")
    return '''{
        "results": [
            {"title": "Attention Is All You Need (Transformer 论文原文)", "url": "https://arxiv.org/abs/1706.03762", "snippet": "The dominant sequence transduction models..."},
            {"title": "The Illustrated Transformer", "url": "https://jalammar.github.io/illustrated-transformer/", "snippet": "A visual and intuitive explanation."}
        ]
    }'''

tool_result = web_search(query=user_request)

# 3. 将用户请求和工具结果拼接,发送给大模型
completion = client.chat.completions.create(
    model="qwen-plus", 
    messages=[
        {'role': 'system', 'content': '你是一位课程研究助理,根据工具结果生成友好的回复。'},
        {'role': 'user', 'content': f'用户原始请求: "{user_request}"\n工具执行结果: {tool_result}'}
    ]
)
print(completion.choices[0].message.content)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

局限性:只适合"有且只有一个工具、每次必须调用"的场景。有多个工具时,谁来决定调哪个?

# 2 意图识别:让 Agent 决定用什么工具

# 2.1 关键词匹配:脆弱的方案

# 伪代码:脆弱的关键词路由器
def route_to_tool(user_input):
    if "论文" in user_input or "arxiv" in user_input:
        return "search_arxiv_paper"
    elif "搜索" in user_input or "资料" in user_input:
        return "web_search"
    else:
        return "no_tool_needed"
1
2
3
4
5
6
7
8

用户说"我想看看那篇 Attention is All You Need 讲了什么",这个路由器就识别不出来了——没有预设关键词。

# 2.2 用大模型来做意图识别

在提示词里列出所有工具,让大模型决定调哪个。这更灵活,但问题是模型返回的结果格式不固定,无法程序化解析。

# 3 结构化输出:让工具调用可靠

核心问题:大模型倾向于生成多样化文本,我需要的是确定性的 JSON 结构。

解决方案是"引导-校验-重试"闭环:

import json
from pydantic import BaseModel, Field, ValidationError, TypeAdapter
from typing import Union, Literal

class WebSearchParams(BaseModel):
    query: str = Field(description="用于网络搜索的关键词。")

class SearchArxivParams(BaseModel):
    query: str = Field(description="用于在 Arxiv.org 上搜索的论文标题或关键词。")

class WebSearchCall(BaseModel):
    tool_name: Literal["web_search"]
    parameters: WebSearchParams

class SearchArxivCall(BaseModel):
    tool_name: Literal["search_arxiv_paper"]
    parameters: SearchArxivParams

ToolCall = Union[WebSearchCall, SearchArxivCall]

def get_structured_output(user_request: str, max_retries: int = 2):
    messages = [{'role': 'user', 'content': build_prompt(user_request)}]
    adapter = TypeAdapter(ToolCall)
    
    for attempt in range(max_retries):
        response = client.chat.completions.create(
            model="qwen-plus", messages=messages, temperature=0
        )
        raw_output = response.choices[0].message.content
        
        try:
            data = json.loads(raw_output.strip('```json').strip('```'))
            validated_data = adapter.validate_python(data)
            return validated_data.model_dump()
        except (json.JSONDecodeError, ValidationError) as e:
            # 验证失败,把错误信息带回给模型让它修正
            messages.extend([
                {'role': 'assistant', 'content': raw_output},
                {'role': 'user', 'content': f"格式错误: {e},请严格按照JSON格式重新输出"}
            ])
    return None
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 4 Function Calling:行业标准

手动实现那套"意图识别 → 结构化输出 → 验证重试"比较繁琐。主流大模型服务商(阿里云、OpenAI 等)已在 API 里内置了这个能力,就是 Function Calling。

import json

tools = [
    {
        "type": "function",
        "function": {
            "name": "search_arxiv_paper",
            "description": "在 Arxiv.org 上搜索学术论文",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "论文的标题或关键词"},
                },
                "required": ["query"],
            },
        }
    }
]

messages = [{"role": "user", "content": "帮我找一下那篇经典的 Transformer 论文 'Attention Is All You Need'"}]
response = client.chat.completions.create(
    model="qwen-plus", messages=messages, tools=tools, tool_choice="auto"
)
response_message = response.choices[0].message

if response_message.tool_calls:
    tool_call = response_message.tool_calls[0]
    function_name = tool_call.function.name
    function_args = json.loads(tool_call.function.arguments)
    
    # 执行工具(模拟)
    tool_result = json.dumps({"paper_id": "1706.03762", "url": "https://arxiv.org/abs/1706.03762"})
    
    # 把工具结果传回给模型,让它生成最终回复
    messages.append(response_message)
    messages.append({"tool_call_id": tool_call.id, "role": "tool", "name": function_name, "content": tool_result})
    
    final_response = client.chat.completions.create(model="qwen-plus", messages=messages)
    print(final_response.choices[0].message.content)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

ReAct = 思考-行动-观察循环:工具调用的结果通过再次 API 调用传给大模型,模型"观察"结果后"思考"是否完成,然后决定"行动"(继续调工具)或返回最终答案。

用 AgentScope 框架可以把这套逻辑大幅简化:

import asyncio
from agentscope.agent import ReActAgent
from agentscope.tool import Toolkit, ToolResponse
from agentscope.model import DashScopeChatModel
from agentscope.message import Msg, TextBlock
from agentscope.formatter import DashScopeChatFormatter

def search_arxiv_paper(query: str) -> ToolResponse:
    """在 Arxiv.org 上搜索学术论文。
    
    Args:
        query (str): 搜索关键词
    """
    print(f"--- [工具执行中] 正在 Arxiv 搜索: {query} ---")
    paper_url = "https://arxiv.org/abs/1706.03762"
    return ToolResponse(content=[TextBlock(type="text", text=f"找到论文 '{query}':{paper_url}")])

async def run_agentscope_example():
    toolkit = Toolkit()
    toolkit.register_tool_function(search_arxiv_paper)
    
    agent = ReActAgent(
        name="Course Research Agent",
        sys_prompt="你是一个课程研究助理,擅长帮人搜集和整理学习资料。",
        model=DashScopeChatModel(model_name="qwen-plus", api_key=os.environ.get("DASHSCOPE_API_KEY")),
        toolkit=toolkit,
        formatter=DashScopeChatFormatter()
    )
    
    msg = Msg(name="user", content="帮我找一下那篇经典的 Transformer 论文 'Attention Is All You Need'", role="user")
    await agent(msg)

await run_agentscope_example()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

AgentScope 相比手动实现的优势:工具只需写带 docstring 的普通函数(自动解析 Schema)、自动处理 tool_calls 解析和多轮调用。

为什么还要学手动实现? 因为框架出问题时需要看底层,生产环境常需加权限验证/缓存/日志等定制逻辑。

# 5 MCP 协议:工具的规模化管理

# 5.1 问题:Schema 硬编码

Function Calling 模式下,我在 Agent 侧硬编码了 web_search 的 JSON Schema。工具一多,维护成本爆炸。工具提供方也没法主动"接入" AI 生态。

# 5.2 MCP 的解耦思想

MCP(Model Context Protocol)的核心:谁提供工具,谁定义工具。

  • MCP Server(工具提供方):声明工具的名称、描述、参数
  • MCP Client(Agent 侧):连接 Server,动态拉取工具清单,无需硬编码 Schema

类比:Function Calling 是主板内部总线,MCP 是 USB 标准接口——后者允许任何第三方设备即插即用。

# 5.3 用 MCP 重构 web_search

Step 1:本地调试(stdio 模式)

# run_mcp_server_example.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("MockWebSearch")

@mcp.tool()
def web_search(query: str, max_results: int = 3) -> str:
    """模拟联网搜索,根据关键词返回搜索结果。
    Args:
        query: 搜索关键词
        max_results: 最大返回结果数量,默认为 3
    """
    # 模拟返回数据...
    return f"搜索「{query}」的模拟结果"

if __name__ == "__main__":
    mcp.run(transport="stdio")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import asyncio, os, sys
from agentscope.agent import ReActAgent
from agentscope.mcp import StdIOStatefulClient
from agentscope.tool import Toolkit
from agentscope.model import DashScopeChatModel
from agentscope.message import Msg
from agentscope.formatter import DashScopeChatFormatter

async def run_local_mcp_example():
    # 1. 启动本地 MCP Server
    web_search_client = StdIOStatefulClient(
        name="web_search_service",
        command=sys.executable,
        args=["run_mcp_server_example.py"],
        cwd=os.getcwd(),
    )
    await web_search_client.connect()
    
    # 2. 工具发现:Client 自动从 Server 拉取工具清单
    toolkit = Toolkit()
    await toolkit.register_mcp_client(web_search_client)
    
    # 3. 创建 Agent
    agent = ReActAgent(
        name="Research Assistant Agent",
        sys_prompt="你是一个课程研究助理。",
        model=DashScopeChatModel(model_name="qwen-plus", api_key=os.environ.get("DASHSCOPE_API_KEY")),
        toolkit=toolkit,
        formatter=DashScopeChatFormatter()
    )
    
    msg = Msg(name="user", content="搜索一下最近关于'大型语言模型'的最新进展。", role="user")
    await agent(msg)
    await web_search_client.close()

await run_local_mcp_example()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

Step 2:接入线上服务(Streamable HTTP 模式)

只需把 Client 从 StdIOStatefulClient 换成 HttpStatelessClient,其他代码不变:

from agentscope.mcp import HttpStatelessClient

client = HttpStatelessClient(
    name="web_search_service",
    transport="streamable_http",
    url="https://dashscope.aliyuncs.com/api/v1/mcps/WebSearch/mcp",
    headers={"Authorization": "Bearer " + os.environ.get("DASHSCOPE_API_KEY")}
)
1
2
3
4
5
6
7
8
  • stdio:Server 和 Client 在同一台机器,适合本地调试(Claude Desktop、Cursor 等 IDE 也用这种方式接入本地 MCP)
  • Streamable HTTP:Server 部署在远端,适合多 Agent/多用户共用同一 MCP Server 的生产环境

# 个人总结

  • 工具调用的演进路径:硬编码 → 关键词匹配 → 大模型意图识别 → 结构化输出 → Function Calling → MCP,每一步都是被上一步的局限性逼出来的
  • Function Calling 本质上是大模型服务商把"引导-校验-重试"内置了,省去了大量手动代码,但理解底层原理是调试的前提
  • ReAct 循环(思考-行动-观察)是 Agent 能"持续干活"的关键机制,单次 Function Calling 只是这个循环的一次迭代
  • MCP 最大的价值是"谁提供谁定义",把工具 Schema 的维护责任从 Agent 开发者转移给工具提供方,大幅降低长期维护成本
  • AgentScope 这类框架值得用,但至少要知道它封装了什么,出了问题才知道去哪里找
编辑 (opens new window)
#大模型#ACP认证#阿里云
从回答问题到解决问题
让 Agent 学会规划与执行

← 从回答问题到解决问题 让 Agent 学会规划与执行→

最近更新
01
feign原理
07-15
02
团队Git分支管理、迭代发布、分批投产标准化规范
07-15
03
面试问题备忘
07-07
更多文章>
Theme by Vdoing | Copyright © 2023-2026 Malize | GitHub | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式