Go代码实战:5步搞定Function Call,手把手教你让AI调用真实API(附完整源码)
Agent 保姆级万字详解·第三章Function Call让 AI 真正动手干活上一章咱们聊清楚了 LLM 的三大硬伤——幻觉、知识滞后、只能输出文字动不了手。也聊了 Agent 的核心公式Agent LLM 规划 记忆 工具。工具就是给大脑装上的手脚没有工具的 Agent 就像一个被困在玻璃瓶里的巨人看着外面精彩的世界干着急。那问题来了怎么让 LLM 能够看见并调用这些工具呢答案就是今天这章的主题——Function Call中文叫函数调用。这是 Agent 落地的第一块基石搞不懂这个后面讲 MCP、讲 Skills 都是空中楼阁。Function Call 到底是什么咱先来一个场景。想象你是个公司的老板请了一个超级顾问。这个顾问智商爆表学富五车什么问题都能给你分析得头头是道。唯一的毛病是——他只会说一口流利的学术英语而你公司里的员工只听得懂中文。你们俩之间完全没法直接沟通。这时候你怎么办请个翻译啊。翻译坐在你们俩中间把你的中文问题翻译成顾问能理解的英文问题把顾问的英文回答再翻译成你能听懂的中文。来来回回翻译几次问题就解决了。Function Call 在 LLM 和外部工具之间扮演的就是这个翻译官的角色。LLM 很聪明它知道查天气是什么意思知道调用 weather API是一个合理的行动。但它不知道怎么写 HTTP 请求不知道 API 的地址是什么不知道参数该怎么组织。Function Call 的作用就是让 LLM 输出一个结构化的指令我要调用哪个函数、参数是什么然后开发者的代码负责真正去执行这个函数把结果返回给 LLM让 LLM 把结果组织成人类能听懂的话。LLM 不需要自己会游泳它只需要知道这个人会游泳可以委托给他。Function Call 就是那个告诉 LLM 谁会游泳的机制。Function Call 的完整闭环光知道比喻还不够咱得把 Function Call 的技术流程掰开了讲。这个流程是整个 Agent 系统的核心骨架你得把它刻进脑子里。第一步用户发起请求用户跟 AI 说“明天北京天气怎么样”这是整个链条的起点。用户用自然语言提出需求可能是问天气、查订单、发邮件、做计算——什么都有可能。第二步LLM 判断要不要调用工具这一步是 Function Call 的精髓。用户说明天北京天气怎么样LLM 拿到这个请求之后它会做几件事看工具列表你的系统给 LLM 提供了一份工具清单清单上写着weather_tool查天气需要 city 参数。LLM 先把这个清单看一遍。判断意图LLM 理解用户想要查天气这正好匹配weather_tool。提取参数LLM 从用户的问句里提取出参数值——北京就是 city 参数的值。输出结构化指令LLM 不是直接回答用户而是输出一个结构化的 JSON大致长这样{name:get_weather,arguments:{city:北京}}这个 JSON 就是 Function Call 的核心产物。它告诉开发者“我要调用 get_weather参数 city 是北京。”LLM 本身不知道 get_weather 具体怎么实现但它准确地识别出了用户需要这个工具并且正确地提取了参数。这就是 Function Call 的神奇之处——它把理解要做什么LLM 的强项和真正去做代码的强项解耦开了。第三步开发者执行函数LLM 输出 Function Call 指令之后控制权交回给开发者的代码。你的代码拿到这个 JSON 之后要做几件事解析函数名看看 LLM 想调哪个工具提取参数把参数从 JSON 里拿出来真正执行调用天气 API获取真实数据格式化结果把 API 返回的数据整理成文本准备反馈给 LLM这一步完全由开发者控制。你可以让代码真的去调天气 API也可以做 mock模拟返回数据用于测试一切都由你说了算。LLM 只负责下指令不负责执行。第四步把结果返回给 LLM执行完函数之后开发者把结果塞回 LLM 的上下文中告诉它“get_weather 返回了结果北京明天晴气温15到22度。”这里有个关键的交互模式需要注意LLM 在收到 Function Call 结果之后会再次思考然后决定怎么回复用户。它可能会直接说北京明天晴天15到22度也可能加一些温馨提示建议穿薄外套——这些说人话的部分都由 LLM 来完成。第五步LLM 组织最终回答最后一步LLM 把结果翻译成用户能听懂的话。这就是 Function Call 的完整闭环用户提问 → LLM 判断是否调用工具 输出函数名和参数 → 开发者代码执行函数 → 结果返回给 LLM → LLM 组织自然语言回答用户整个过程中LLM 和代码各司其职LLM 做理解、做推理、做决策代码做执行、做 IO、做真实世界的数据交互。两者配合天衣无缝。实战用 Go 实现一个天气查询 Function Call说完了理论咱来点实际的。下面这段代码基于 Go 1.22调用兼容 OpenAI 格式的 API。为了避免手写 SSE 流式解析的坑我们直接使用目前最流行的开源库——github.com/sashabaranov/go-openai。这个库在 GitHub 上有将近两万颗星维护活跃基本 cover 了所有 OpenAI 兼容接口的细节。你只需要替换 API Key 和地址就能直接跑起来。代码分三块讲结构定义 → 核心逻辑 → main 入口。第一部分安装依赖go mod init weather-agent go get github.com/sashabaranov/go-openailatest第二部分完整可运行代码packagemainimport(contextfmtosgithub.com/sashabaranov/go-openai)// executeFunction 根据函数名执行对应的真实逻辑// 这里演示了如何处理天气查询的函数调用funcexecuteFunction(functionNamestring,argumentsstring)(string,error){switchfunctionName{caseget_weather:// 解析参数提取城市名// go-openai 会把参数解析好传过来这里直接用字符串解析即可// 真实场景里可以用 json.Unmarshal 反序列化returnfetchWeather(arguments)default:return,fmt.Errorf(未知函数: %s,functionName)}}// fetchWeather 模拟调用天气 API// 真实项目中这里应该调用和风天气、OpenWeatherMap 等真实接口funcfetchWeather(citystring)(string,error){// 简化处理从参数字符串中提取城市名// 实际应用中建议用 json 反序列化得到干净的 city 字符串weatherDB:map[string]string{北京:北京晴气温15~22°C西北风2-3级空气质量优,上海:上海多云气温18~25°C东风1-2级空气质量良,广州:广州雷阵雨气温24~30°C南风3-4级空气质量中,深圳:深圳阴天气温22~28°C东南风2级空气质量良,}ifweather,ok:weatherDB[city];ok{returnweather,nil}returnfmt.Sprintf(%s暂无天气数据,city),nil}// buildToolDefinitions 构建工具列表// 这里统一定义所有可以被 LLM 调用的工具// go-openai 的 Tool 结构已经很清晰了直接照着写就行funcbuildToolDefinitions()[]openai.Tool{return[]openai.Tool{{Type:openai.ToolTypeFunction,Function:openai.FunctionDefinition{Name:get_weather,Description:查询指定城市的天气信息包括温度、天气状况、风力、空气质量等,Parameters:openai.FunctionJSONParameters{Type:object,Properties:map[string]openai.FunctionJSONProperty{city:{Type:string,Description:城市名称用中文如北京、上海、广州、深圳,},},Required:[]string{city},},},},}}funcmain(){// ---------- 1. 读取配置 ----------apiKey:os.Getenv(OPENAI_API_KEY)apiURL:os.Getenv(OPENAI_API_URL)ifapiKey{fmt.Println(【错误】请设置环境变量 OPENAI_API_KEY)return}// 如果没有设置代理地址默认使用 OpenAI 官方地址// 国产模型请替换为对应的兼容端点地址如硅基流动等ifapiURL{apiURLhttps://api.openai.com/v1}// 创建 OpenAI 客户端// go-openai 支持自定义 base URL可以兼容任何 OpenAI 格式的 APIcfg:openai.DefaultConfig(apiKey)cfg.BaseURLapiURL client:openai.NewClientWithConfig(cfg)fmt.Println(【用户提问】明天北京天气怎么样需要穿外套吗)fmt.Println(【等待 LLM 响应...】\n)// ---------- 2. 构造对话上下文 ----------// 初始化对话历史第一条是用户的提问messages:[]openai.ChatCompletionMessage{{Role:openai.ChatMessageRoleUser,Content:明天北京天气怎么样需要穿外套吗,},}// 工具列表告诉 LLM 它可以调用哪些函数tools:buildToolDefinitions()// ---------- 3. 调用 LLM ----------ctx:context.Background()// 第一次请求resp,err:client.Chat(ctx,openai.ChatCompletionRequest{Model:gpt-4,Messages:messages,Tools:tools,})iferr!nil{fmt.Printf(【错误】LLM 请求失败: %v\n,err)return}// 获取 LLM 的回复assistantMsg:resp.Choices[0].Message fmt.Printf(【LLM 回复内容】%v\n,assistantMsg)// 把助手的消息加到对话历史里messagesappend(messages,assistantMsg)// ---------- 4. 检查是否触发了 Function Call ----------// 如果 LLM 返回了 tool_calls说明它想调用工具ifassistantMsg.ToolCalls!nillen(assistantMsg.ToolCalls)0{// 遍历所有要调用的工具通常一次只调一个for_,call:rangeassistantMsg.ToolCalls{funcName:call.Function.Name funcArgs:call.Function.Arguments fmt.Printf(\n【Function Call 触发】调用函数: %s参数: %s\n,funcName,funcArgs)// 执行真正的函数逻辑result,err:executeFunction(funcName,funcArgs)iferr!nil{fmt.Printf(【错误】执行函数失败: %v\n,err)continue}fmt.Printf(【函数执行结果】%s\n,result)// 把工具执行结果追加到对话上下文// 注意role 必须是 toolcontent 是执行结果tool_call_id 要对应上messagesappend(messages,openai.ChatCompletionMessage{Role:openai.ChatMessageRoleTool,Content:result,ToolCallID:call.ID,})}// ---------- 5. 把工具结果再次发给 LLM让它组织最终回答 ----------fmt.Println(\n【再次请求 LLM组织最终回答】)resp2,err:client.Chat(ctx,openai.ChatCompletionRequest{Model:gpt-4,Messages:messages,Tools:tools,})iferr!nil{fmt.Printf(【错误】LLM 二次请求失败: %v\n,err)return}finalAnswer:resp2.Choices[0].Message.Content fmt.Printf(\n【LLM 最终回答】%s\n,finalAnswer)}else{// 如果没有触发 Function Call直接输出 LLM 的回复fmt.Printf(\n【LLM 直接回答】%s\n,assistantMsg.Content)}}代码运行说明这段代码要跑起来一共就三步安装依赖需要 Go 1.21go mod init weather-agent go get github.com/sashabaranov/go-openailatest设置环境变量# Linux / macOSexportOPENAI_API_KEY你的真实密钥exportOPENAI_API_URLhttps://api.openai.com/v1# 官方地址国产模型填对应的兼容端点# Windows PowerShell$env:OPENAI_API_KEY你的真实密钥$env:OPENAI_API_URLhttps://api.openai.com/v1运行go run main.go如果一切正常你会看到完整的输出【用户提问】明天北京天气怎么样需要穿外套吗 【等待 LLM 响应...】 【LLM 回复内容】{Role:assistant Content: Tools:0xc000... ToolCalls:[0xc000...]} 【Function Call 触发】调用函数: get_weather参数: {city:北京} 【函数执行结果】北京晴气温15~22°C西北风2-3级空气质量优 【再次请求 LLM组织最终回答】 【LLM 最终回答】根据查询结果明天北京是晴天气温在15到22度之间早晚温差较大建议带一件薄外套出门。整个流程就是这样LLM 判断要查天气 → 代码执行查天气的真实逻辑 → 把结果反馈给 LLM → LLM 组织了一段贴心的回答。代码里用到的关键点我逐一解释一下openai.Tool这是 go-openai 封装的工具定义结构体对应 OpenAI 的tools参数。把工具的名称、描述、参数 schema 填好LLM 就知道这个工具是干什么的、怎么调用。ToolCalls当 LLM 决定调用工具时它会返回一个tool_calls数组里面有函数名function.name和参数function.arguments是 JSON 字符串。注意这里是 JSON 字符串不是 map你需要二次解析。openai.ChatMessageRoleTool工具执行完之后的返回消息role 必须填tool并且要带上tool_call_id告诉 LLM 这个结果是哪个调用的返回值。两轮请求这是 Function Call 的标准模式。第一轮 LLM 分析用户意图并决定调用工具第二轮带着工具的执行结果再发一次请求LLM 才能看到结果并组织最终回答。Function Call 的局限性Function Call 很好用但它不是银弹。在实际项目中你会发现几个让人头疼的问题。第一个问题每个模型厂商的 Function Call 实现不一样。OpenAI 有 OpenAI 的格式Claude 有 Claude 的格式国产模型又是另一套。你在这个模型上写的工具定义换到那个模型上可能就跑不通了。同一个 Function Call你得写多套适配代码维护成本直接翻倍。第二个问题工具定义没法复用。你给模型 A 定义了一套天气查询工具现在想给模型 B 也加上同样的能力对不起你得重新写一遍。工具的定义和模型是紧耦合的换模型就得重来。第三个问题缺乏统一的标准协议。现在的 Agent 开发有点像互联网早期——每家网站自己定义自己的协议互相不兼容。你做一个工具只能给自己的 Agent 用想给别人用不好意思重写一遍吧。这三个问题听起来很眼熟对不对没错当年的 HTTP 协议就是为了解决各家网站协议不兼容的问题而诞生的。那么AI 时代的HTTP 协议是什么答案就是下一章我们要讲的——MCP 协议Model Context Protocol。这是由 Anthropic 主推的一个开放标准目标是让 AI 模型和工具之间有一个统一的接口规范彻底解决互操作性的问题。MCP 到底是怎么回事它跟 Function Call 是什么关系企业落地应该怎么用下一章全部给你讲透。三连催更咱们不见不散作者利威尔xu一个正在死磕 AI 应用落地、热爱分享的普通后端开发。如果这篇文章对你有帮助欢迎点赞、收藏、关注更多硬核内容持续更新中。

相关新闻

FreeCAD MCP 建模核心详解:create_object 与 edit_object 的全部参数一次讲透

FreeCAD MCP 建模核心详解:create_object 与 edit_object 的全部参数一次讲透

FreeCAD MCP 建模核心详解:create_object 与 edit_object 的全部参数一次讲透 【免费下载链接】freecad-mcp FreeCAD MCP(Model Context Protocol) server 项目地址: https://gitcode.com/gh_mirrors/fr/freecad-mcp FreeCAD MCP 是一个 Model Context Proto…

2026/10/3 20:04:46 阅读更多 →
AI绘画提示词的分层写法:一份 70 张成图的字段统计,颠覆了我的直觉

AI绘画提示词的分层写法:一份 70 张成图的字段统计,颠覆了我的直觉

我手上有 70 张成系列的图,提示词是逐字段记录的。我把它们全部统计了一遍,结论和我一开始的直觉完全相反。下面这几条,每一条都能直接改掉你的写法。 一、真正「统一」的,是那几个从来没变过的字段 同一批 70 张里,…

2026/10/3 20:03:45 阅读更多 →
网速测速次次满分,打开网页、APP却总转圈?90%人都忽略的网络隐形短板

网速测速次次满分,打开网页、APP却总转圈?90%人都忽略的网络隐形短板

很多家庭网速存在一个百思不解的硬伤:每次打开测速工具,下载、上传速率直接跑满千兆,延迟数据漂亮,看起来是满分满血网络。但日常使用完全不匹配:点开网页空白转圈、APP加载半天、短视频开头卡顿、微信图片加载延迟、切…

2026/10/3 20:03:45 阅读更多 →

最新新闻

VRChat头像性能优化:避免“贪多”工程,打造高评分角色

VRChat头像性能优化:避免“贪多”工程,打造高评分角色

VRChat 里有一个很常见的日文词叫“よくばり”,翻译过来就是“贪多”。这个词用来形容一类头像工程再合适不过:一个人物模型里想同时塞进 4K 贴图、几十个待机动作、满身 PhysBone、粒子特效、换装部件,甚至再挂一个音乐播放器。结果往往是模…

2026/10/3 20:45:45 阅读更多 →
DeepSeek Harness 开源贡献手记:从零到合入主线

DeepSeek Harness 开源贡献手记:从零到合入主线

1. 引言:为什么参与开源贡献 本文记录我参与 DeepSeek Harness 开源项目的完整过程,从发现问题、定位源码、编写补丁到最终合入主线的真实经历,希望能为同样想参与开源贡献的开发者提供一份可参考的路线图。 2. 项目背景与初步调研 在动手…

2026/10/3 20:40:42 阅读更多 →
面试官:MySQL中的 distinct 和 group by 哪个效率更高?

面试官:MySQL中的 distinct 和 group by 哪个效率更高?

一、开篇:一道高频面试题背后的问题在 MySQL 相关的面试中,有一道题经常被面试官问到:distinct 和 group by 都能去重,它们哪个效率更高?很多候选人听到这个问题后会下意识地回答「distinct 更快,因为它的语…

2026/10/3 20:40:42 阅读更多 →
面试官:BIO、NIO、AIO 的区别是什么?

面试官:BIO、NIO、AIO 的区别是什么?

一、开篇:从一个面试场景说起面试官经常会抛出一个看似简单、实则非常考察底层功底的题目:「说说 BIO、NIO、AIO 的区别」。很多同学能背出「BIO 是阻塞、NIO 是非阻塞、AIO 是异步非阻塞」,但如果继续追问「为什么 NIO 是非阻塞的」「底层分…

2026/10/3 20:40:41 阅读更多 →
Python实现绘制同切圆

Python实现绘制同切圆

程序源码:# 绘制同切圆 import turtle as t # 导入turtle绘图库,取别名t t.pensize(3) # 设置画笔粗细为3像素 t.circle(10) # 画半径为10的圆 t.circle(20) # 画半径为20的圆 t.circle(40) …

2026/10/3 20:39:41 阅读更多 →
数据管理与论文写作并行:按阶段推进的研究节奏怎么排

数据管理与论文写作并行:按阶段推进的研究节奏怎么排

数据工作和论文写作挤在同一段时间里,几乎是每位研究生都会遇到的排期难题。多数人卡住的不是不会写,而是两条线的节拍没有对齐。我们在梳理用户反馈时发现,把研究数据与论文写作按成熟度切成四段、给每段设定明确的两线配比,返工…

2026/10/3 20:39:41 阅读更多 →

日新闻

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南

把回忆蒸馏成 AI 的浪漫实验:为什么你需要前任.skill 完整指南 【免费下载链接】ex-skill 前任 skill 项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill 前任.skill 是一个运行在 Claude Code 上的开源 Skill:导入微信、iMessage、短信、…

2026/10/3 0:00:27 阅读更多 →
45个经典Linux面试题:从命令到网络排障的完整考点解析

45个经典Linux面试题:从命令到网络排障的完整考点解析

刚开始带应届生的时候,我最头疼的就是他们拿着一摞Linux面试题背得滚瓜烂熟,一上机全露馅。后来自己从被面的人变成面别人的人,才慢慢摸清楚:Linux面试题考的根本不是答案本身,而是你面对一个不确定的系统问题时&#…

2026/10/3 0:01:28 阅读更多 →
SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

SAP生产预留实战指南:MB21/MB23/MB25协同与MRP集成

简介:本资源是一份面向SAP ABAP开发人员、生产计划专员及ERP实施顾问的实操型操作指南,聚焦SAP生产预留核心业务场景,系统解决物料预留创建、查询、校验与批量处理等高频问题。文档以结构化方式覆盖预留背景原理、OMC2编码规则、工厂级参数配…

2026/10/3 0:01:28 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/3 9:14:33 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/3 9:47:50 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/10/3 9:42:31 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/2 10:36:31 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:35 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/3 9:42:36 阅读更多 →