
Function Calling 实战在 generative-ai-for-beginners 课程中用 Azure OpenAI 构建结构化数据与外部 API 调用【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners本文基于 generative-ai-for-beginners 课程第 11 课《Integrating with Function Calling》完整讲解 Function Calling函数调用的原理与落地方法为什么 LLM 的原始响应格式不一致、如何通过 Responses API 的tools机制让模型输出严格受约束的结构化参数、以及如何把模型返回的function_call项映射到你自己的 Python 函数并二次调用模型生成自然语言答案。读完本文你可以复刻课程中的课程推荐聊天机器人完整场景Azure OpenAI Microsoft Learn Catalog API并理解 TypeScript / JavaScript 变体示例中同一模式的工程化写法。场景与目标为教育聊天机器人接入课程检索课程设定了一个具体场景为一家教育初创公司的聊天机器人增加功能让用户能够用自然语言查找技术课程机器人根据学习者的技能水平、当前角色和感兴趣的技术推荐课程。整个方案由三部分组成Azure OpenAI创建用户的聊天体验Microsoft Learn Catalog API根据用户请求帮助查找课程Function Calling接收用户查询将其发送到一个函数去发起 API 请求。对应地本课的学习目标是解释使用 function calling 的目的、使用 Azure OpenAI Service 配置函数调用、为自己的应用场景设计有效的函数调用。为什么需要 Function Calling两个核心痛点在 Function Calling 出现之前LLM 的响应是非结构化且不稳定的开发者必须编写复杂的校验代码来处理响应的各种变体同时模型受限于训练数据的截止时间用户无法得到斯德哥尔摩现在的天气如何这类实时答案。Function Calling 是 Azure OpenAI Service 的一项特性专门用来克服以下两个局限一致的响应格式Consistent response format如果能更好地控制响应格式就可以更 easily 地把响应集成到下游的其他系统外部数据External data能够在聊天上下文中使用应用其他来源的数据。用一个可复现的例子看清格式不一致问题课程推荐用 aoai-assignment.ipynb 跑下面的场景。假设我们要建立一个学生数据库以便向学生推荐合适的课程先看两段数据内容非常相似的学生描述第 1 步建立到 Azure OpenAI 资源的连接。import os import json from openai import OpenAI from dotenv import load_dotenv load_dotenv() # The Responses API is served from the Azure OpenAI (Microsoft Foundry) v1 # endpoint, so we point the OpenAI client at your-endpoint/openai/v1/. endpoint os.environ[AZURE_OPENAI_ENDPOINT] client OpenAI( api_keyos.environ[AZURE_OPENAI_API_KEY], base_urlf{endpoint.rstrip(/)}/openai/v1/, ) deploymentos.environ[AZURE_OPENAI_DEPLOYMENT]注意这里的连接方式因为走的是v1 端点endpoint/openai/v1/只需要设置api_key和base_url不需要再传api_version。需要配置的环境变量有三个AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY、AZURE_OPENAI_DEPLOYMENT。第 2 步用两个变量创建两段学生描述。student_1_descriptionEmily Johnson is a sophomore majoring in computer science at Duke University. She has a 3.7 GPA. Emily is an active member of the universitys Chess Club and Debate Team. She hopes to pursue a career in software engineering after graduating. student_2_description Michael Lee is a sophomore majoring in computer science at Stanford University. He has a 3.8 GPA. Michael is known for his programming skills and is an active member of the universitys Robotics Club. He hopes to pursue a career in artificial intelligence after finishing his studies.第 3 步构造两个完全相同的提示词指示 LLM 我们关心哪些信息并要求返回 JSON 对象prompt1 f Please extract the following information from the given text and return it as a JSON object: name major school grades club This is the body of text to extract the information from: {student_1_description} prompt2 f Please extract the following information from the given text and return it as a JSON object: name major school grades club This is the body of text to extract the information from: {student_2_description} 第 4 步用client.responses.create把提示词发送给 LLM。提示词存放在input参数中角色role赋值为user模拟用户向聊天机器人发消息# response from prompt one openai_response1 client.responses.create( modeldeployment, input [{role: user, content: prompt1}], storeFalse, ) openai_response1.output_text # response from prompt two openai_response2 client.responses.create( modeldeployment, input [{role: user, content: prompt2}], storeFalse, ) openai_response2.output_text第 5 步用json.loads把响应转成 JSON 对象# Loading the response as a JSON object json_response1 json.loads(openai_response1.output_text) json_response1两次请求的响应分别类似{ name: Emily Johnson, major: computer science, school: Duke University, grades: 3.7, club: Chess Club }{ name: Michael Lee, major: computer science, school: Stanford University, grades: 3.8 GPA, club: Robotics Club }尽管提示词完全相同、描述也很相似grades字段的取值格式却不同有时是3.7有时是3.7 GPA。这就是问题的根源——LLM 接收非结构化的文本提示返回的也是非结构化的数据而存储或使用这份数据时我们需要一个结构化的格式才能知道应该预期什么。所以如何解决格式问题答案是函数调用使用 function calling 可以确保拿回结构化数据。这里有一个关键概念必须澄清——使用 function calling 时LLM 并不会真正调用或执行任何函数。实际上是我们为 LLM 创建一个响应所遵循的结构结构 schemaLLM 按这个结构输出要调哪个函数、传什么参数然后由我们的应用程序依据这些结构化响应决定去运行哪个函数。Function Calling 的三类典型用例调用外部工具Calling External Tools聊天机器人擅长回答用户问题。借助 function calling聊天机器人可以利用用户的消息完成某些任务。例如学生可以要求机器人给我的导师发一封邮件说我在这一科上需要更多帮助这会触发对send_email(to: string, body: string)的函数调用。创建 API 或数据库查询Create API or Database Queries用户可以用自然语言查找信息信息被转换为格式化的查询或 API 请求。例如教师请求哪些学生完成了上次作业可以调用名为get_completed(student_name: string, assignment: int, current_status: string)的函数。创建结构化数据Creating Structured Data用户可以拿一段文本或 CSV用 LLM 从中提取关键信息。例如学生可以把一篇关于和平协议的维基百科文章转换为 AI 记忆卡片这需要用到函数get_important_facts(agreement_name: string, date_signed: string, parties_involved: list)。创建第一个函数调用三步走创建函数调用的过程包含 3 个主要步骤调用Calling调用 Responses API携带你的函数tools列表和一条用户消息读取Reading读取模型的响应以执行一个动作即执行某个函数或 API 调用再次调用Making携带函数返回的响应再调用一次 Responses API用这些信息创建对用户的最终响应。Step 1 — 创建消息第一步是创建一条用户消息。它可以动态赋值例如取文本输入框的值也可以直接在这里赋值。第一次使用 Responses API 时需要为消息定义role和contentrole可以是system创建规则、assistant模型或user最终用户对函数调用而言我们把它赋值为user并给一个示例问题。messages [ {role: user, content: Find me a good course for a beginner student to learn Azure.} ]分配不同的角色能让 LLM 清楚地知道是系统在说话还是用户在说话这有助于构建一段 LLM 可以继续在此基础上展开的对话历史。Step 2 — 创建函数定义接下来定义函数及其参数。这里只用一个函数search_courses但你也可以创建多个。重要函数会包含在发给 LLM 的系统消息中并占用你可用的 token 额度。因此函数定义不宜过多、过冗。下面是把函数创建为一个条目数组的代码。每个条目是 Responses API扁平flat格式的一个工具顶层属性为type、name、description和parametersfunctions [ { type:function, name:search_courses, description:Retrieves courses from the search index based on the parameters provided, parameters:{ type:object, properties:{ role:{ type:string, description:The role of the learner (i.e. developer, data scientist, student, etc.) }, product:{ type:string, description:The product that the lesson is covering (i.e. Azure, Power BI, etc.) }, level:{ type:string, description:The level of experience the learner has prior to taking the course (i.e. beginner, intermediate, advanced) } }, required:[ role ] } } ]各属性含义逐条拆解name— 希望被调用的函数名description— 对函数如何工作的描述。这里务必具体、清晰模型正是靠它判断何时调用该函数parameters— 希望模型在响应中产出的取值列表与格式。parameters 由条目组成每个条目有type— 属性存储所用的数据类型properties— 模型在响应中将使用的具体取值列表其中每个属性又包含name键名模型在格式化响应中使用的属性名例如producttype该属性的数据类型例如stringdescription该属性的描述。此外还有一个可选属性required— 完成函数调用所必需的属性。示例中required: [role]表示role参数必须有值模型缺参时无法完成调用。Step 3 — 发起函数调用定义好函数后需要把它包含进 Responses API 的调用中方法是给请求加上tools本例即toolsfunctions。同时可以把tool_choice设为auto意思是让 LLM 根据用户消息自行判断该调用哪个函数而不是我们自己指定。response client.responses.create(modeldeployment, inputmessages, toolsfunctions, tool_choiceauto, storeFalse) print(response.output)现在返回的响应中response.output里包含一个function_call项形如{ type: function_call, name: search_courses, call_id: call_abc123, arguments: {\n \role\: \student\,\n \product\: \Azure\,\n \level\: \beginner\\n} }可以看到函数search_courses被调用、以及arguments属性中列出的具体参数。这说明 LLM 成功找到了匹配函数各参数所需的数据——它们正是从 Responses API 调用中input参数提供的值里提取出来的。回顾一下messages的值messages [ {role: user, content: Find me a good course for a beginner student to learn Azure.} ]student、Azure、beginner从这条消息中被提取出来并设置为函数的输入。以这种方式使用函数既是从提示中提取信息的好方法也是为 LLM 提供结构、并获得可复用功能的好方法。把函数调用集成到应用中测试了 LLM 的格式化响应之后就可以把它集成到实际应用里。第一步从响应中提取 function_call 项response_items response.output tool_calls [item for item in response_items if item.type function_call]第二步定义真正执行外部调用的 Python 函数这里定义一个调用 Microsoft Learn API 获取课程列表的函数import requests def search_courses(role, product, level): url https://learn.microsoft.com/api/catalog/ params { role: role, product: product, level: level } response requests.get(url, paramsparams) modules response.json()[modules] results [] for module in modules[:5]: title module[title] url module[url] results.append({title: title, url: url}) return str(results)注意我们现在创建了一个真实的 Python 函数其名字与functions变量中引入的函数名一一对应并且发起了真正的外部 API 调用来获取所需数据这里是查训练模块的 Microsoft Learn API。第三步建立函数定义 → 真实函数的映射并执行functions变量只是给 LLM 看的 schema真正的执行靠下面的映射表。要判断是否需要调用 Python 函数需检查 LLM 响应中是否包含function_call项然后调用它指向的函数# Check if the model wants to call a function if tool_calls: for tool_call in tool_calls: print(Recommended Function call:) print(tool_call.name) print() # Call the function. function_name tool_call.name available_functions { search_courses: search_courses, } function_to_call available_functions[function_name] function_args json.loads(tool_call.arguments) function_response function_to_call(**function_args) print(Output of function call:) print(function_response) print(type(function_response)) # Add the function call and its result back to the conversation. # The models function_call item must be appended before its output. messages.append(tool_call) # the assistants function_call item messages.append( # the function result { type: function_call_output, call_id: tool_call.call_id, output: function_response, } )其中三行是提取函数名、解析参数、发起调用的核心function_to_call available_functions[function_name] function_args json.loads(tool_call.arguments) function_response function_to_call(**function_args)这里有两个值得注意的工程细节available_functions是一个字典映射把 LLM 可能返回的函数名映射到真实可执行的 Python 函数仓库中的 JavaScript 变体示例还在此基础上加了一层白名单校验用Object.prototype.hasOwnProperty.call检查函数名是否存在于允许列表中不存在则直接抛错这是一种防止模型返回任意函数名时的安全实践执行完成后必须把tool_call项和function_call_output结果按顺序追加回messagesfunction_call 项必须先于其输出且call_id要与模型返回的一致模型才能把结果对应上。实际运行上面的代码典型输出类似OutputRecommended Function call: { name: search_courses, arguments: {\n \role\: \student\,\n \product\: \Azure\,\n \level\: \beginner\\n} } Output of function call: [{title: Describe concepts of cryptography, url: https://learn.microsoft.com/training/modules/describe-concepts-of-cryptography/?WT.mc_idapi_CatalogApi}, {title: Introduction to audio classification with TensorFlow, url: https://learn.microsoft.com/en-us/training/modules/intro-audio-classification-tensorflow/?WT.mc_idapi_CatalogApi}, {title: Design a Performant Data Model in Azure SQL Database with Azure Data Studio, url: https://learn.microsoft.com/training/modules/design-a-data-model-with-ads/?WT.mc_idapi_CatalogApi}, {title: Getting started with the Microsoft Cloud Adoption Framework for Azure, url: https://learn.microsoft.com/training/modules/cloud-adoption-framework-getting-started/?WT.mc_idapi_CatalogApi}, {title: Set up the Rust development environment, url: https://learn.microsoft.com/training/modules/rust-set-up-environment/?WT.mc_idapi_CatalogApi}] class str第四步把函数结果送回 LLM生成自然语言答案现在把更新后的messages再次发送给 LLM就能拿到自然语言响应而不是 API 的 JSON 格式响应print(Messages in next request:) print(messages) print() second_response client.responses.create( inputmessages, modeldeployment, tool_choiceauto, toolsfunctions, temperature0, storeFalse, ) # get a new response from the model where it can see the function response print(second_response.output_text)注意第二次调用设置了temperature0让模型的生成尽可能确定便于稳定地把课程列表整理成最终答案。典型输出I found some good courses for beginner students to learn Azure: 1. [Describe concepts of cryptography](https://learn.microsoft.com/training/modules/describe-concepts-of-cryptography/?WT.mc_idapi_CatalogApi) 2. [Introduction to audio classification with TensorFlow](https://learn.microsoft.com/en-us/training/modules/intro-audio-classification-tensorflow/?WT.mc_idapi_CatalogApi) 3. [Design a Performant Data Model in Azure SQL Database with Azure Data Studio](https://learn.microsoft.com/training/modules/design-a-data-model-with-ads/?WT.mc_idapi_CatalogApi) 4. [Getting started with the Microsoft Cloud Adoption Framework for Azure](https://learn.microsoft.com/training/modules/cloud-adoption-framework-getting-started/?WT.mc_idapi_CatalogApi) 5. [Set up the Rust development environment](https://learn.microsoft.com/training/modules/rust-set-up-environment/?WT.mc_idapi_CatalogApi) You can click on the links to access the courses.到这里用户提问 → 模型输出结构化函数调用 → 应用执行函数访问外部 API → 结果回传 → 模型生成自然语言答案的完整闭环就跑通了。仓库中的多语言实现对照TypeScript 与 JavaScript 变体同一套 Function Calling 模式在仓库里还有两个可直接运行的变体实现适合作为对照阅读材料。TypeScript 版天气查询应用main.ts 演示了查天气场景用户问New York 天气如何摄氏模型调用findWeather工具应用用该参数请求 Bing Maps API 取回实时天气。从源码结构看它与 Python 主示例共享完全一致的三步模式但有几处工程化细节值得借鉴环境变量强校验启动时检查AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY、BING_MAPS_BASE_URL、BING_API_KEY四个变量缺失即抛错main.tsHTTPS URL 校验isValidUrl确保两个端点必须是https:协议main.ts扁平工具 schema与 README 一致工具定义顶层直接是type/name/description/parametersmain.ts且给unit参数加了enum: [C, F]约束取值范围安全的外部调用参数经URLSearchParams编码防注入、请求带 10 秒超时、错误日志不打印可能含敏感信息的完整错误对象main.ts健壮的参数解析遍历result.output找function_call项时JSON.parse包在 try/catch 里解析失败或缺少必需的location参数都会跳过而不是崩溃main.ts。其运行配置见 package.json依赖openai^4.77.0、axios、dotenvnpm run build执行rimraf build tscnpm start通过 nodemon 以npx ts-node ./src/main.ts运行。JavaScript 版航班/酒店查询Azure AI Inference SDKapp.js 展示了与 Responses API 不同的另一条路径——通过azure-rest/ai-inference的ModelClient走/chat/completions接口使用经典的嵌套工具格式tools项内含function对象见 app.js并演示了一次注册多个工具getFlightInfo与getHotelInfo通过finish_reason tool_calls判断模型是否要求调用工具并读取message.tool_calls[0]函数执行后把结果以role: tool、携带tool_call_id的消息追加回对话历史再发起第二次请求获得最终回答app.js。文件头注释中列出了该示例注释里声明的、支持 tools 的模型示例如 OpenAI 的 gpt-4o-mini、gpt-4o以及 Cohere、Mistral 系列适用前提是模型本身支持工具调用。两个变体需要的环境变量分别是AZURE_INFERENCE_CREDENTIAL/AZURE_INFERENCE_ENDPOINTJavaScript 版与上文的四个变量TypeScript 版运行前按各自 package.json 安装依赖即可。练习扩展你的函数调用课程给出的进阶练习可直接对照 aoai-assignment.ipynb 最后一节的 Code Challenge为函数增加更多参数帮助学习者找到更多课程可查阅 Microsoft Learn Catalog API 的开发者参考文档了解可用参数再创建一个新的函数调用采集更多学习者信息例如他们的母语当函数调用和/或 API 调用没有返回合适的课程时创建错误处理逻辑。小结Function Calling 的本质是LLM 不执行函数只按你定义的 schema 输出该调用哪个函数、带什么参数真正执行函数、访问外部 API 的是你的应用程序。掌握这一模式后你可以同时解决 LLM 应用中的两大痛点——响应格式不一致用参数 schema 约束输出结构与数据时效性/来源受限把模型接到实时 API 上。本课的完整闭环是定义toolsschema →tool_choiceauto让模型决策 → 解析function_call项 → 经函数映射表执行外部调用 → 以function_call_output或 chat 格式的tool消息把结果回传 → 二次请求模型生成自然语言答案。仓库中 Python 主示例、TypeScript 天气应用 与 JavaScript 航班查询 三份实现分别为这一闭环提供了不同 SDK 与不同工具格式下的完整参照。【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考