十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LLM工具调用全解:Function Call / MCP / CLI 原理与实战

LLM工具调用全解:Function Call / MCP / CLI 原理与实战 第一部分 理论篇三种工具调用模式原理与对比1.1 为什么需要工具调用大模型天生的三类短板没有工具的llm有三大局限知识时效性。训练数据有截止日期不知道今天天气、最新股价、实时库存确定性计算。算术、计数、复杂逻辑常常出错且自信地给错答案行动能力。本身无法订票、发邮件、写文件、查数据库、调用 API1.2 Function Call — 意图生成层模型原生函数调用Function Call 是大模型原生支持的能力模型在理解用户意图后不直接“编答案”而是输出一个结构化的调用指令函数名 参数 JSON交由宿主程序去执行真实工具再把结果喂回模型生成最终回复。工具的定义需要包含 函数名、功能描述、参数约束例子如下{name:get_weather,description:查询某城市天气,parameters:{type:object,properties:{city:{type:string},unit:{enum:[C,F]}},required:[city]}}优点模型原生能力接入成本低结构化JSON输出可靠易解析工具走白名单安全可控支持并行 / 链式调用编排局限各家API略不同不通用工具需逐个手动注册维护累依赖description质量易幻觉调用 / 捏造参数1.3 MCP(Model Context Protocol) — 协议接入层标准化协议MCP (Model Context Protocol) 是 Anthropic 于 2024 年提出的开放标准协议规范了“模型宿主”与“外部工具/数据源”之间如何发现、描述、调用和鉴权。目标是让任何工具只需实现一次 MCP Server就能被任何支持 MCP 的宿主Claude Desktop、IDE、各类 Agent 框架直接使用。优点开放标准跨模型/产品复用M×N → MN工具动态发现Host/Client/Server 解耦清晰协议级鉴权与安全治理已有丰富生态 (FS/DB/Git…)局限需实现/部署 Server接入中等协议有学习曲线长连接/生命周期管理较复杂生态早期第三方质量参差跨网络部署增加运维成本1.4 CLI — 执行实现层命令行作为通用工具把 shell 命令行作为模型可调用的“通用工具”模型生成一段命令字符串宿主在受控环境中执行把 stdout/stderr 回传给模型解读。CLI 本质是“一个通用工具”通常仍由 Function Call 触发模型发出 run_shell(cmd) 调用宿主执行命令。优点能力极强一条shell命令就可以完成文件读写、查询数据库、运行脚本、网络请求等大量任务不用逐个开发独立工具函数覆盖范围广系统自带成千上万现成命令无需从零实现功能开发成本低不用为每一项操作单独定义Function‑Call函数与Schema灵活自由支持管道、通配符、循环脚本可完成复杂链式操作局限 风险高危安全风险模型幻觉很容易生成删除文件、修改系统配置、访问敏感数据等破坏性命令结果不稳定命令输出格式自由无约束返回文本容易难以解析高幻觉概率模型容易写出语法错误、不存在的命令状态难以管控shell存在会话上下文、工作目录、环境变量等隐式状态多轮调用容易出错权限管控复杂需要沙箱、容器严格隔离执行环境防止越权操作1.5 三者分层关系、优缺点与选型决策维度Function CallMCPCLI本质模型能力/API通信协议/标准通用工具实现层次意图生成层协议接入层执行实现层制定方各家模型商Anthropic开放标准OS/Shell 生态工具来源开发者手动注册Server 动态发现系统已有命令接入成本低中极低灵活性中中极高安全可控高(白名单)高(协议级)低(需沙箱)跨模型复用否是是真实系统往往是三者叠加 —— 各取所长流程链路用户需求 → Function Call生成调用意图 → MCP发现鉴权 → CLI/工具执行操作场景AClaude Desktop MCPFunction Call 触发 → MCP Server 提供文件/DB 工具 → Server 内部执行场景BClaude Code (Agentic CLI)Function Call 触发 run_shell → 沙箱执行命令 → 结果回灌场景C企业Agent平台Function Call 意图 → MCP 鉴权网关 → 转发到CLI/具名工具执行第二部分 实战篇可运行项目实操指南2.1 项目概述与能力说明2.2 模式一Function Call 运行流程将api以“说明书”的方式发给llm供llm调用。{type:function,function:{name:search_annual_report,description:(在A股年报语料库中检索与问题最相关的段落。知识库仅收录 5 家公司贵州茅台(600519)/五粮液(000858)/宁德时代(300750)/海康威视(002415)/中国平安(601318)年份仅 2021/2022/2023。不在库内的公司请勿调用本工具。),parameters:{type:object,properties:{query:{type:string,description:(检索问题自然语言。重要不要包含公司名和年份已由 stock_code/year 参数过滤只用简短财务术语例如 营收和净利润、研发投入、主营业务。把公司名写进 query 会稀释检索精度。),},stock_code:{type:string,description:可选按公司过滤如 300750。不传则跨公司检索,},year:{type:string,description:可选按年份过滤2021 / 2022 / 2023,},top_k:{type:integer,description:返回段落数默认5建议不超过10,},},required:[query],},},}2.3 模式二MCP 服务端调用流程先实现mcp server# 注意用 as 别名导入后端函数避免下方同名 tool 函数遮蔽后递归调用自己fromsrc.rag_backendimport(# noqa: E402search_annual_reportas_search_annual_report,list_companiesas_list_companies,)frommcp.server.fastmcpimportFastMCP mcpFastMCP(rag-server)mcp.tool()defsearch_annual_report(query:str,stock_code:str|NoneNone,year:str|NoneNone,top_k:int5,)-str: 在A股年报语料库中检索与问题最相关的段落。 知识库仅收录 5 家公司贵州茅台(600519)/五粮液(000858)/ 宁德时代(300750)/海康威视(002415)/中国平安(601318) 年份仅 2021/2022/2023。不在库内的公司请勿调用本工具。 Args: query: 检索问题。重要不要包含公司名和年份已由 stock_code/year 过滤 只用简短财务术语例如 营收和净利润、研发投入、主营业务。 把公司名写进 query 会稀释检索精度。 stock_code: 可选按公司过滤如 300750。 year: 可选按年份过滤2021 / 2022 / 2023。 top_k: 返回段落数默认5建议不超过10。 Returns: 按相关度排序的段落列表每段含来源公司、年份、章节、页码。 return_search_annual_report(query,stock_code,year,top_k)然后在client里面发现mcp# ── 连接所有 Server一次走完 建管道→握手→发现工具→转 schema ───────────────asyncdefconnect_all_servers(stack:AsyncExitStack): 连接所有 MCP Server返回 (tool_registry, openai_tools) tool_registry : tool_name → (ClientSession, server_label)用于路由 call_tool openai_tools : 转成 OpenAI tools schema 的列表直接喂给 LLM print(正在连接 MCP Servers...\n,filesys.stderr)tool_registry:dict[str,tuple[ClientSession,str]]{}openai_tools:list[dict][]forlabel,paramsinbuild_server_configs().items():# stdio_client 建立进程间通信管道子进程的 stdin/stdoutread,writeawaitstack.enter_async_context(stdio_client(params))session:ClientSessionawaitstack.enter_async_context(ClientSession(read,write))# initialize() MCP 握手协商协议版本和能力awaitsession.initialize()# list_tools() 工具发现同时把 MCP inputSchema 适配成 OpenAI parameters# —— 这一步是协议层 → 模型层的转换MCP 让工具与模型解耦# 但喂给具体 LLM 时仍要变成它认识的格式inputSchema 本就是 JSON Schema直接塞tools_resultawaitsession.list_tools()fortoolintools_result.tools:tool_registry[tool.name](session,label)openai_tools.append({type:function,function:{name:tool.name,description:tool.descriptionor,parameters:tool.inputSchemaor{type:object,properties:{}},},})print(f ✓ [{label}]{, .join(t.namefortintools_result.tools)},filesys.stderr)print(f\n共{len(tool_registry)}个工具就绪\n,filesys.stderr)returntool_registry,openai_tools再把工具发给llm让llm调用respclient.chat.completions.create(modelmodel,messagesmessages,toolsopenai_tools,tool_choiceauto,)收到llm的调用请求后client端执行调用# 查路由表找到对应 Server 的 ClientSession跨进程调用session,labeltool_registry.get(name,(None,None))ifsessionisNone:resultf未知工具{name}else:# call_tool() MCP 协议的 tools/call 请求工具在 Server 子进程内执行call_resultawaitsession.call_tool(name,args)result\n.join(b.textforbincall_result.contentifhasattr(b,text))2.4 模式三CLI两种形态白名单模式 / 沙箱模式CLI 方式的核心思想是把能力做成普通命令行工具——它本身跟大模型没有任何关系可以像 ls、git 一样独立使用然后再让大模型通过一个 run_cli/run_bash 工具去调用它。所以下面分两步看先把它当普通 CLI 用再把它接给模型。先写脚本安装命令行工具。pip install -e .# pyproject.toml[project]namefincliversion0.1.0descriptionFunction Call / MCP / CLI 三方式对比教学项目 —— fincli 命令行requires-python3.10dependencies[openai1.0.0,faiss-cpu1.7.4,numpy1.24.0,httpx0.25.0,mcp1.0.0,]# 这一行把 mode_cli.cli.main:main 注册为 PATH 上的可执行命令 fincli# pip install -e . 之后fincli 就和 git/ls 一样可在任意目录直接调用[project.scripts]finclimode_cli.cli.main:main[build-system]requires[setuptools61]build-backendsetuptools.build_meta[tool.setuptools]packages[src,mode_cli,mode_cli.cli]安装成功后就可以用命令行的方式来执行了fincli list-companies白名单方式下LLM 调 run_cli(command‘rag_search’, args{…})host 按 NAMED_COMMANDS 白名单拼出 fincli search … 执行。specNAMED_COMMANDS.get(command)ifspecisNone:returnf[run_cli] 未知命令{command}白名单{list(NAMED_COMMANDS)})python mode_cli/run_cli.py --mode named -q 宁德时代2023年营收和净利润沙箱模式下LLM 自己拼完整 shell 命令字符串如 fincli search --query ‘营收和净利润’ --stock-code 300750 --year 2023host 经 sandbox_check 后 subprocess.run(shellTrue) 执行。最灵活也最危险靠沙箱兜底。拦截器defsandbox_check(command:str)-Optional[str]:返回 None 表示通过返回字符串表示拒绝原因。# 拦截管道/链式符号ifCHAIN_PATTERN.search(command):return沙箱拦截禁止使用 ; | 等链式/重定向符号forpatinDANGEROUS_PATTERNS:ifre.search(pat,command,re.IGNORECASE):returnf沙箱拦截命中危险模式{pat!r}try:tokensshlex.split(command,posixFalse)exceptValueError:return沙箱拦截命令解析失败ifnottokens:return沙箱拦截空命令headPath(tokens[0]).name.lower()ifheadnotinALLOWED_HEADS:returnf沙箱拦截{tokens[0]!r}不在白名单{sorted(ALLOWED_HEADS)}中returnNone拦截效果python-cfrom mode_cli.run_cli import run_bash; print(run_bash(rm -rf /))[run_bash]沙箱拦截命中危险模式\\brm\\b
返回列表