generative-ai-for-beginners 函数调用实战:用 Azure OpenAI Function Calling 实现结构化响应与外部数据集成

发布时间:2026/9/7 4:28:27
generative-ai-for-beginners 函数调用实战:用 Azure OpenAI Function Calling 实现结构化响应与外部数据集成 generative-ai-for-beginners 函数调用实战用 Azure OpenAI Function Calling 实现结构化响应与外部数据集成【免费下载链接】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 课保加利亚语版教程展开完整覆盖 Function Calling 的核心概念、三步创建流程与应用集成代码你将学会为什么 LLM 的“自由发挥”式响应难以被下游系统消费如何用functions定义 function_callauto让模型输出严格结构化的函数调用并把它接入真实 APIMicrosoft Learn Catalog完成一个教育类课程推荐聊天机器人的完整闭环。课程导读与学习目标本课是“构建聊天应用”之后的进阶内容针对两类典型痛点如何获得更一致的响应格式让响应在下游系统API、数据库中更容易被处理如何为应用接入其他数据源进一步丰富聊天机器人能力。读完本课你将能够解释使用 Function Calling 的目的与适用场景使用 Azure OpenAI Service 配置一次函数调用为你的应用用例设计高效的函数调用。本课建议配合仓库中的交互式笔记本实操保加利亚语版配套笔记本。该笔记本与下文所有代码一一对应已逐格核对代码模式完全一致。场景用函数改进教育创业公司的聊天机器人本课的业务场景为一个教育初创公司构建功能让用户通过聊天机器人查找技术课程系统根据用户的技能水平、当前角色和感兴趣的技术推荐课程。实现该场景组合了三种能力Azure OpenAI为用户创建聊天体验Microsoft Learn Catalog API根据用户请求帮助查找课程Function Calling接收用户查询并将其交给一个发起 API 请求的函数。先回答一个前置问题我们为什么需要 Function Calling为什么需要函数调用两个核心限制在函数调用出现之前LLM 的响应是非结构化且不一致的。开发者不得不编写复杂的校验代码来处理每一种可能的响应变体用户也无法得到“斯德哥尔摩现在几点了”这类答案因为模型受限于训练数据的时间边界。Function Calling 是 Azure OpenAI Service 的一项功能用于克服以下限制一致的响应格式。如果能控制响应格式就能更容易地将响应集成到其他下游系统中外部数据。能够在聊天上下文中使用应用的其他数据源。用场景演示问题LLM 返回 JSON 格式的不一致性下面用一个“学生数据库”例子演示响应格式问题我们想创建一份学生数据以便为其推荐课程。准备两段结构非常相似的学生描述import os import json from openai import AzureOpenAI from dotenv import load_dotenv load_dotenv() client AzureOpenAI( api_keyos.environ[AZURE_OPENAI_API_KEY], # 这也是默认值可省略 api_version 2023-07-01-preview ) deploymentos.environ[AZURE_OPENAI_DEPLOYMENT]上面是配置 Azure OpenAI 连接的 Python 代码其中设置了api_key与api_version注意本课保加利亚语版跟随配套笔记本使用AzureOpenAISDK 的chat.completions接口仓库英文版同一课已迁移到tools/tool_choice参数风格的新接口见 英文原版第 11 课两种写法流程思想完全一致。接着创建两段学生描述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.这两段描述随后会被发给 LLM 进行解析数据之后可存入应用、发送到 API 或写入数据库。现在创建两个内容完全相同的提示词指示 LLM 我们关心哪些信息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} 提示词要求 LLM 提取信息并以 JSON 格式返回。提示词与连接就绪后把请求发给 LLM# response from prompt one openai_response1 client.chat.completions.create( modeldeployment, messages [{role: user, content: prompt1}] ) openai_response1.choices[0].message.content # response from prompt two openai_response2 client.chat.completions.create( modeldeployment, messages [{role: user, content: prompt2}] ) openai_response2.choices[0].message.content提示词被存放在messages变量中并赋以user角色以模拟用户写给聊天机器人的一条消息。两个响应都可以按openai_response1.choices[0].message.content找到。最后用json.loads把响应转为 JSON 对象# Loading the response as a JSON object json_response1 json.loads(openai_response1.choices[0].message.content) json_response1响应 1{ name: Emily Johnson, major: computer science, school: Duke University, grades: 3.7, club: Chess Club }响应 2{ name: Michael Lee, major: computer science, school: Stanford University, grades: 3.8 GPA, club: Robotics Club }关键观察尽管提示词完全相同、描述结构也相似grades属性却出现了不同的格式——有时是3.7有时是3.7 GPA。原因是 LLM 接收的是以提示词形式写入的非结构化数据返回的也是非结构化数据。要在存储或使用这些数据前知道“该期待什么”就必须有一个结构化格式。函数调用如何解决格式问题通过函数调用可以确保拿到结构化数据。这里有一个必须澄清的概念LLM 并不会真正调用或执行任何函数。实际机制是我们为 LLM 的响应创建一套结构函数签名与参数 schemaLLM 按照该结构输出“调用意图”然后由我们的应用代码读取结构化输出决定在应用中执行哪个函数。之后我们把函数的真实执行结果再发回给 LLMLLM 会用自然语言回答用户的原始查询。函数调用的三类典型应用场景函数调用可以改进很多类型的应用例如调用外部工具聊天机器人擅长回答用户问题借助函数调用机器人可以用用户消息执行特定任务。例如学生请求“给我的导师发一封邮件说明我需要在这门课上获得更多帮助”机器人可发起函数调用send_email(to: string, body: string)创建 API 或数据库查询用户可以用自然语言表达需求再被转换为格式化查询或 API 请求。例如教师询问“哪些学生完成了最后一次作业”可调用函数get_completed(student_name: string, assignment: int, current_status: string)生成结构化数据用户可以把一段文本或 CSV 交给 LLM 提取关键信息。例如学生把一篇关于和平协议的维基百科文章转换成 AI 闪卡对应函数get_important_facts(agreement_name: string, date_signed: string, parties_involved: list)。创建你的第一次函数调用三步流程创建一次函数调用包含三个主要步骤调用带着你的函数列表和用户消息调用聊天补全 API读取解析模型响应以执行动作即运行函数或发起 API 请求再调用带着函数的返回结果再次调用 API让模型基于这些信息生成面向用户的回复。步骤 1创建消息第一步是创建用户消息既可以动态地从文本输入框取值也可以直接在这里赋固定值。首次接触聊天补全 API 时需要为消息定义role和content两个字段。role可以是system制定规则、assistant模型或user最终用户。函数调用场景下我们将其设为user并给出一个示例问题messages [ {role: user, content: Find me a good course for a beginner student to learn Azure.} ]通过分配不同角色LLM 能清楚地区分“系统在说话”还是“用户在说话”这有助于构建一段 LLM 可以持续累积的对话历史。步骤 2创建函数接下来定义函数及其参数。本例只用一个函数search_courses但你可以创建多个。重要函数会被包含在发给 LLM 的系统消息中因此会计入你可用的 token 预算。函数以数组形式创建每个元素是一个函数具有name、description、parameters属性functions [ { 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你希望模型在响应中产出的取值与格式列表各元素含type— 属性所存储值的数据类型此处为objectproperties— 模型将使用的具体取值列表每个属性含name键名模型在格式化响应中使用的属性名例如producttype该属性的数据类型例如stringdescription对该属性的具体描述。此外还有可选属性required完成函数调用所必需的属性。上例中只有role是必填项product和level可选。步骤 3执行函数调用定义函数后需要把它包含进 API 请求——做法是在请求中加入functions参数本例为functionsfunctions。还可以将function_call设为auto表示让 LLM 自行决定基于用户消息调用哪个函数而不是由我们强制指定response client.chat.completions.create(modeldeployment, messagesmessages, functionsfunctions, function_callauto) print(response.choices[0].message)返回的响应如下{ role: assistant, function_call: { name: search_courses, arguments: {\n \role\: \student\,\n \product\: \Azure\,\n \level\: \beginner\\n} } }可以看到search_courses被“调用”以及以什么参数被调用——即 JSON 响应中arguments属性里那段 JSON 字符串。结论LLM 从传入messages参数的值中提取出了匹配函数参数的数据。回顾一下messages的值messages [ {role: user, content: Find me a good course for a beginner student to learn Azure.} ]student、Azure、beginner三个词被从消息中抽取出来并作为函数输入。这样使用函数既是从提示词中抽取信息的优秀手段也是给 LLM 施加结构、获得可复用功能的方式。下一步看看如何在应用里真正用起来。在应用中集成函数调用管理流程测试过 LLM 的格式化响应后就可以把它集成进应用了。第 1 步保存响应消息先调用服务并把响应消息存入变量response_message response.choices[0].message第 2 步定义真正执行外部请求的 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 检索培训模块只取前 5 条模块的标题与链接最后以字符串形式返回——因为回填给 LLM 的content需要是字符串。第 3 步判断是否要调用函数并分发执行要判断是否调用 Python 函数需要检查 LLM 响应中是否包含function_call若包含则调用其指定的函数# Check if the model wants to call a function if response_message.function_call.name: print(Recommended Function call:) print(response_message.function_call.name) print() # Call the function. function_name response_message.function_call.name available_functions { search_courses: search_courses, } function_to_call available_functions[function_name] function_args json.loads(response_message.function_call.arguments) function_response function_to_call(**function_args) print(Output of function call:) print(function_response) print(type(function_response)) # Add the assistant response and function response to the messages messages.append( # adding assistant response to messages { role: response_message.role, function_call: { name: function_name, arguments: response_message.function_call.arguments, }, content: None } ) messages.append( # adding function response to messages { role: function, name: function_name, content:function_response, } )其中这三行完成了“提取函数名、解析参数、执行调用”的核心分发逻辑function_to_call available_functions[function_name] function_args json.loads(response_message.function_call.arguments) function_response function_to_call(**function_args)available_functions字典把“LLM 声明的函数名”映射到“本应用真实可执行的函数对象”这是模型意图与应用代码之间的桥梁json.loads把模型输出的arguments字符串解析成字典再经**展开为关键字参数。执行后我们把两样东西追加回messages模型的助手消息含function_call字段、content为None以及role为function的函数结果消息——这构成完整的调用上下文。实际运行输出如下Recommended 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可以看到函数返回的是一个字符串化的列表class str。第 4 步把函数结果送回 LLM换取自然语言回复最后把更新后的messages现在包含用户消息、函数调用消息、函数结果消息再次发给 LLM此时会得到自然语言回复而非 JSON 格式print(Messages in next request:) print(messages) print() second_response client.chat.completions.create( messagesmessages, modeldeployment, function_callauto, functionsfunctions, temperature0 ) # 获取模型的新响应此时模型可以看到函数的返回结果 print(second_response.choices[0].message)输出{ role: assistant, content: I found some good courses for beginner students to learn Azure:\n\n1. [Describe concepts of cryptography] (https://learn.microsoft.com/training/modules/describe-concepts-of-cryptography/?WT.mc_idapi_CatalogApi)\n2. [Introduction to audio classification with TensorFlow](https://learn.microsoft.com/en-us/training/modules/intro-audio-classification-tensorflow/?WT.mc_idapi_CatalogApi)\n3. [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)\n4. [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)\n5. [Set up the Rust development environment](https://learn.microsoft.com/training/modules/rust-set-up-environment/?WT.mc_idapi_CatalogApi)\n\nYou can click on the links to access the courses. }至此闭环完成用户自然语言 → 结构化函数调用 → 真实 API 数据 → 自然语言答复。源码佐证仓库中同一模式的 JavaScript 实现与安全实践本仓库在同一主题下还提供了一个 JavaScript 示例 js-githubmodels/app.js用 Azure AI Inference SDK 实现了与上文完全同构的“模型 → 工具调用 → 本地函数 → 回填对话”流程可以从中印证几个关键工程细节响应判定方式JS 示例通过response.body.choices[0].finish_reason tool_calls判断模型要求调用工具见 app.js#L127-L134这与 Python 版“检查response_message.function_call.name是否非空”是等价的防御式判断——不能假设模型一定会调用函数必须显式检查白名单校验模型返回的函数名来自模型生成内容属于不可信输入。JS 示例在分发前先校验函数名是否存在于允许的映射表中不存在则直接抛错见 app.js#L141-L145安全解析参数对arguments字符串的 JSON 解析包裹在try/catch中解析失败时抛出带上下文信息的错误见 app.js#L147-L153。从源码结构看这些细节正是把教程中的available_functions字典分发模式落地到生产时的加固方向函数名白名单 参数解析错误处理。此外JS 示例中工具定义采用{type: function, function: {...}}的嵌套结构与 Python 版的扁平functions数组字段一一对应印证了两种 SDK 只是序列化形态不同函数 schemaname/description/parameters/required的语义完全一致。多函数场景下JS 示例同时注册了getFlightInfo与getHotelInfo两个工具并依赖模型的tool_choice自动选择——这与本课“可以创建多个函数、由 LLM 决定调用哪个”的说明相互印证。延伸作业为继续深入 Azure OpenAI Function Calling教程建议尝试构建以下扩展为函数增加更多参数帮助学习者找到更多课程可参考 Microsoft Learn Catalog API 的开发者参考文档了解可用参数创建另一个函数调用从学习者处采集更多维度信息例如母语native language当函数调用和/或 API 调用没有返回合适课程时编写错误处理逻辑这一点可参考上文 JS 示例的安全解析写法。小结与继续学习本课的完整技术主线可以概括为用functions定义把“响应结构”从提示词技巧升级为可校验的 schema用function_callauto让模型负责抽取参数用应用侧的函数映射字典负责真实执行最后把执行结果回填对话由模型生成自然语言答复。它同时解决了响应格式一致性与外部数据接入两个问题是构建可靠 AI 应用的基础机制。完整可运行代码见 保加利亚语版配套笔记本对照阅读 英文原版第 11 课新版 SDK 接口风格与 JavaScript 示例下一课设计 AI 应用的用户体验第 12 课保加利亚语版。【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考