Generative AI for Beginners 第 11 课用 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函数调用Function Calling是 Azure OpenAI 服务的一项能力它让大语言模型LLM不再只是凭训练数据作答而是能够按开发者声明的 JSON 结构产出稳定、可解析的响应再由你的应用代码决定调用哪个真实函数查数据库、请求外部 API 等最后把结果回传给模型生成自然语言回复。在 generative-ai-for-beginners 仓库的第 11 课中它被用于教育创业项目的实战场景让用户通过聊天机器人按技能水平、当前角色与兴趣产品检索微软官方技术课程。读完本文你将理解函数调用的原理与典型用例掌握从定义函数声明到执行真实 Python 函数再到把结果喂回模型的完整三步流程并学会把它集成进自己的应用。本课完整配套代码在 translations/cs/11-integrating-with-function-calling/python/aoai-assignment.ipynb可直接运行的 Jupyter Notebook你既可以动手执行也可以按下面的讲解逐步跟进。一、为什么需要函数调用两个绕不开的痛点在前面的课程中你已经学会了不少生成式 AI 能力但有两个问题仍然待解响应格式不稳定LLM 返回的文本是非结构化、不统一的开发者必须编写大量校验代码来兼容每一种输出变体才能把响应交给下游系统处理。无法获取外部数据模型受限于训练数据的截止时间与范围用户问不出斯德哥尔摩现在的天气如何这类需要实时数据的问题。函数调用正是 Azure OpenAI 用来突破这两点的能力一致的响应格式Consistent response format更好地控制输出格式就能更方便地把响应集成到下游其他系统外部数据External data在对话上下文中能够使用应用中其他来源的数据。注意使用函数调用时LLM 本身并不会真正执行任何函数。它做的只是按照你声明的结构去组织响应真正决定调用哪个函数并执行的逻辑写在你自己的应用代码里。这是理解整个机制最关键的一点。二、用场景演示问题同构输入为何输出不一致要理解函数调用的价值先看一个具体的反例。假设我们要构建一个学生信息库用于给学生推荐合适的课程。下面两个学生描述在信息构成上高度相似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。为此先建立 Azure OpenAI 连接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_version2023-07-01-preview ) deployment os.environ[AZURE_OPENAI_DEPLOYMENT]这份代码需要你提前配置AZURE_OPENAI_API_KEY与AZURE_OPENAI_DEPLOYMENT环境变量可通过 00-course-setup 的本地环境章节了解配置方式。接着构造两条完全一致的提示词要求模型把关心的字段提取成 JSONprompt1 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} 随后用client.chat.completions.create把提示词作为user角色消息发送给模型模拟用户向聊天机器人发消息# 第一个提示词的响应 openai_response1 client.chat.completions.create( modeldeployment, messages[{role: user, content: prompt1}] ) openai_response1.choices[0].message.content # 第二个提示词的响应 openai_response2 client.chat.completions.create( modeldeployment, messages[{role: user, content: prompt2}] ) openai_response2.choices[0].message.content通过openai_response1[choices][0][message][content]可以查看返回内容。最后用json.loads把响应转成 JSON 对象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.8 GPA两种格式——前者是数值后者带了单位后缀。根因在于LLM 输入的是写在提示词里的非结构化文本返回的也必然是非结构化文本。当我们想把数据存储或复用时必须有确定的结构才能知道接下来拿到的一定是什么。函数调用正是用来解决这个格式化问题的方案——为 LLM 声明一套响应结构由应用依据结构化响应决定调用哪个函数。三、函数调用的典型使用场景在动手之前先看函数调用能让应用变强的几类场景调用外部工具Calling External Tools聊天机器人擅长回答问题借助函数调用它可以把用户消息转化为执行某个具体任务的动作。例如学生说给老师发封邮件说我这门课需要更多帮助即可触发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)完成。四、创建第一个函数调用完整三步流程本课场景需要三样东西协同工作用Azure OpenAI提供对话体验用Microsoft Learn Catalog API帮用户按需检索课程用函数调用接收用户查询并把参数交给真实函数去发 API 请求。一次完整的函数调用由三个主步骤构成调用带着函数清单声明和用户消息调用 Chat Completions API读取读取模型返回中指示的动作应执行哪个函数 / API 请求再次调用把真实函数的执行结果追加进消息再调一次 Chat Completions API让模型据此组织给用户的自然语言回答。步骤 1创建用户消息第一步是构造一条用户消息其值既可以从文本框动态读取也可以像下面这样直接赋值。消息需要两个字段role与content。role有三种取值——system设定规则、assistant模型、user终端用户。函数调用场景下我们把它设为user并给一个示例问题messages [{role: user, content: Find me a good course for a beginner student to learn Azure.}]通过区分不同角色LLM 能清楚知道哪句话来自系统、哪句来自用户从而构建可持续追加的对话历史。步骤 2声明函数与参数结构接下来定义函数名和它的参数。本课只声明一个函数search_courses但你可按需声明多个。这里的关键机制是函数声明会连同系统消息一起发给 LLM因此会占用你可用的 token 额度声明越多、描述越长基础 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希望模型点名调用的函数名需与后面真实 Python 函数名一一对应description对该函数用途的描述。这里越具体、越清晰模型越容易在恰当的时候选择它parameters希望模型在响应中按此结构与格式生成参数的清单其内部包含type参数对象的数据类型本课为objectproperties模型会使用的具体字段列表其中每个字段又包含name字段键名即模型在格式化响应里使用的属性名如producttype该字段的数据类型如stringdescription对该字段含义的说明此外还有可选的required标明完成这次函数调用所必需的字段例如本例中role必填而product、level可选。步骤 3发起带函数声明的调用定义好函数后需要在 Chat Completion 请求里带上它。做法是传入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被点名且arguments字段里带着一份参数 JSON。模型之所以能把参数填准是因为它从本次调用传入的messages里抽取了信息——回顾一下消息内容是 Find me a good course for abeginner studentto learnAzure于是student、Azure、beginner被分别映射到了role、product、levelmessages [{role: user, content: Find me a good course for a beginner student to learn Azure.}]这种方式既是从提示词抽取信息的利器也为 LLM 提供了确定的结构约束让函数具备可复用性。下一步就是把这套机制真正接进应用。五、把函数调用集成进应用程序前面验证了格式化响应现在把它接入真实应用。整体流程管理分为四步。第一步保存模型返回的消息对象先调用 OpenAI 服务并把结果存到response_message变量中供后续判断使用response_message response.choices[0].message第二步编写对应的真实 Python 函数现在定义一个真实 Python 函数search_courses它会向 Microsoft Learn API 发起外部请求检索培训模块。注意它的参数签名role, product, level必须与上一步声明的functions中的名字一一对应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)函数内部做了这些事拼装https://learn.microsoft.com/api/catalog/地址把三个参数作为查询串发出 GET 请求从返回 JSON 的modules数组中取出前 5 条抽出title与url以字符串形式返回列表。第三步判断是否需要调用函数并完成调度声明变量functions是给模型看的说明书Python 函数是真正干活的实现怎么把它们对接起来答案是检查模型响应中是否包含function_call有则据此调用对应的 Python 函数# 判断模型是否想调用某个函数 if response_message.function_call.name: print(Recommended Function call:) print(response_message.function_call.name) print() # 调用该函数 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)) # 把 assistant 的响应和函数响应都追加回 messages messages.append( # 追加 assistant 响应 { role: response_message.role, function_call: { name: function_name, arguments: response_message.function_call.arguments, }, content: None } ) messages.append( # 追加函数响应 { 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字典把模型点名的函数名映射到真实的 Python 可调用对象再用json.loads把argumentsJSON 字符串解析成字典最后通过**function_args展开为关键字参数完成调用。程序运行输出如下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/}, {title: Introduction to audio classification with TensorFlow, url: https://learn.microsoft.com/training/modules/intro-audio-classification-tensorflow/}, {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/}, {title: Getting started with the Microsoft Cloud Adoption Framework for Azure, url: https://learn.microsoft.com/training/modules/cloud-adoption-framework-getting-started/}, {title: Set up the Rust development environment, url: https://learn.microsoft.com/training/modules/rust-set-up-environment/}] class str这里有两个细节值得注意消息追加顺序有讲究先追加 assistant 的角色与function_callcontent置None再追加role: function的真实函数结果这样模型才能把它要求的调用与调用返回的数据对应起来追加的两类消息都是对话上下文的一部分第二次请求必须把它们一并带上。第四步用函数结果生成自然语言回复最后把更新过的messages再次发给 LLM让它基于真实课程数据用自然语言而非 API 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]\n2. [Introduction to audio classification with TensorFlow]\n3. [Design a Performant Data Model in Azure SQL Database with Azure Data Studio]\n4. [Getting started with the Microsoft Cloud Adoption Framework for Azure]\n5. [Set up the Rust development environment]\n\nYou can click on the links to access the courses. }至此一次完整的用户查询 → 结构化函数调用 → 真实 API 数据 → 自然语言回复闭环就打通了。六、对照当前仓库函数调用的两代 API 形态捷克语课程文档与其配套 notebook 属于较早期的实现使用上面讲解的 Chat Completions 参数形态functions、function_callauto、消息中追加role:function。这是 translations/cs/11-integrating-with-function-calling/python/aoai-assignment.ipynb 与 同目录 oai-assignment.ipynb面向非 Azure 的 OpenAI中的写法可直接对照运行。与此同时仓库根目录的英文课程已经随 SDK 演进迁移到较新的Responses API核心差异可留意调用入口从client.chat.completions.create改为client.responses.create客户端通过base_urlf{endpoint}/openai/v1/指向 v1 端点工具声明从嵌套结构改为扁平 schema每个工具顶层直接携带type值为function、name、description、parameters四个字段请求参数从functions/function_call变为tools/tool_choice响应中的调用项与上下文追加格式也随之改变从response.output中筛出item.type function_call把模型的 function_call 项与{type:function_call_output,call_id:...,output:...}一起追加回messages。想参考新版写法的读者可以直接对照同一课根目录的实现11-integrating-with-function-calling/python/aoai-assignment.ipynbResponses API 版的完整可运行 notebookTypeScript 示例 main.ts声明findWeather工具、校验环境变量与 URL、解析function_call并调用 Bing Maps API 的完整参考其中还包含超时防止请求挂起参数安全解析等工程化细节js-githubmodels/app.js面向 GitHub Models 的 JavaScript 版本多个工具对象并列传入tools: [tool, hotels]。仓库内的tests/与shared/提供了环境变量、API 工具与输入校验等可复用封装参见 shared/python/env_utils.py可作为把函数调用接入真实服务时的工程化参考。七、课后作业与练习方向要进一步吃透 Azure OpenAI Function Calling可以自己动手扩展为search_courses增加更多参数让学习者能检索到更精准的课程新增一个函数调用获取更多学习者信息例如其母语再据此给出推荐增加错误处理当函数调用或 API 调用没有返回任何合适课程时的兜底逻辑。提示可查阅 Microsoft Learn Catalog API 的开发者参考文档弄清这些数据以何种字段、在何处可用。八、本课要点速览函数调用解决两大核心问题响应格式不可控与模型无法接触外部数据它输出的只是结构化声明真正执行函数的是你的应用代码。一次完整调用包含三次往返带函数清单发消息 → 解析模型返回的function_call→ 把真实函数结果回传模型生成最终自然语言回答。函数声明由name、description、parameters含type/properties/ 可选required构成且会占用 token 额度描述应具体清晰参数名与真实 Python 函数签名必须保持一致。集成时按保存响应 → 实现同名 Python 函数 → 用字典映射并**function_args展开调用 → 顺序追加消息 → 二次请求的流程落地。若你的代码环境已升级 SDK请对照仓库新版 notebook 改用 Responses API 的tools/tool_choice扁平 schema 写法。完成本课后可继续学习第 12 课《为 AI 应用设计 UX》12-designing-ux-for-ai-applications/README.md。【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考