ARTICLE DETAIL

资讯详情

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

在 generative-ai-for-beginners 中实战函数调用(Function Calling):从非结构化响应到课程推荐 Chatbot 的完整实现

在 generative-ai-for-beginners 中实战函数调用(Function Calling):从非结构化响应到课程推荐 Chatbot 的完整实现 在 generative-ai-for-beginners 中实战函数调用Function Calling从非结构化响应到课程推荐 Chatbot 的完整实现【免费下载链接】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 课的《Függvényhívás integrálása》函数调用集成文档展开系统讲解函数调用的动机、消息与函数定义规范、三步调用流程以及如何把 LLM 的结构化响应映射回真实 Python 函数并二次请求模型生成自然语言答案。读完本文你可以完整复现“自然语言 → 结构化参数 → 外部 API 调用 → 自然语言回答”的闭环并将同一套模式迁移到仓库中附带的 TypeScript 与 JavaScript 示例上。本课覆盖内容与学习目标该文档translations/hu/11-integrating-with-function-calling/README.md承接前几课的内容指出两个待解决的问题其一如何获得更一致的响应格式以便下游系统更容易处理其二如何引入外部数据源来丰富应用。本课围绕这两个问题展开具体覆盖解释什么是函数调用Function Calling以及它的使用场景使用 Azure OpenAI 创建一个函数调用将函数调用集成到一个应用中。学完本课之后你将能够解释为什么要使用函数调用通过 Azure OpenAI 服务配置函数调用为你的应用用例设计高效的函数调用。场景用函数改进我们的 Chatbot本课为一家教育类初创公司构建一个功能用户通过 Chatbot 搜索技术课程系统根据用户的技术水平、当前角色和感兴趣的技术推荐课程。整个场景由三部分组合完成Azure OpenAI为用户创建聊天体验Microsoft Learn Catalog API根据用户请求帮助用户查找课程函数调用把用户的查询交给一个函数由该函数发起 API 请求。为什么需要函数调用在函数调用出现之前LLM 的响应是非结构化且不稳定的。开发者不得不编写复杂的校验代码来处理每一种响应变体用户也无法得到诸如“斯德哥尔摩现在天气如何”这样的答案因为模型只能使用其训练截止日期之前的数据。函数调用是 Azure OpenAI 服务的一项能力专门用来克服以下两个局限一致的响应格式更好地控制响应格式后可以更容易地把响应集成到其他下游系统中外部数据能够在聊天上下文中使用来自应用其他数据源的信息。通过一个场景演示问题学生数据提取建议配合可运行的笔记本 translations/hu/11-integrating-with-function-calling/python/aoai-assignment.ipynb 运行下面的场景。如果你只是阅读也不必担心——这段示例的目的是展示一个可以被函数调用解决的问题。假设我们要建立一个学生数据库以便为每个学生推荐合适的课程。下面有两条包含非常相似数据的学生描述。第一步创建 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_version 2023-07-01-preview ) deploymentos.environ[AZURE_OPENAI_DEPLOYMENT]这是配置 Azure OpenAI 连接的 Python 代码通过环境变量AZURE_OPENAI_API_KEY与AZURE_OPENAI_DEPLOYMENT提供密钥与部署名称并指定api_version。运行前置条件是设置好这两个环境变量可通过.env文件由load_dotenv()加载。第二步准备两段结构相近的学生描述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 或存入数据库。第三步构造两个完全相同的提示词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完成提示词与 Azure OpenAI 连接后使用openai.ChatCompletion发送请求。提示词存放在messages变量中角色设置为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第五步解析 JSON 并观察格式不一致最后通过json.loads把响应转换为 JSON 对象# 将响应作为 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.7 GPA。原因在于 LLM 收到的是提示词形式提供的非结构化数据返回的也必然是非结构化数据。而要存储或使用这些数据我们必须事先知道它们的确切结构。那么如何从根本上解决格式问题使用函数调用可以确保拿到结构化数据。需要澄清一个常见误解使用函数调用时LLM 并不会真正调用或执行任何函数。我们实际上是创建了一个供 LLM 在回答中遵循的结构然后利用这些结构化响应在应用中决定执行哪个函数。拿到函数的执行结果后再把它回传给 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)这样的函数实现。创建你的第一个函数调用创建一个函数调用的过程包含三个主要步骤携带你的函数列表和一条用户消息调用 Chat Completions API读取模型的响应执行相应操作——即运行一个函数或一次 API 调用携带你执行函数的结果再次调用 Chat Completions API利用这些信息生成对用户的最终回答。步骤 1创建消息第一步是创建一条用户消息。这个值可以动态取自文本输入框也可以像这里一样直接赋值。首次使用 Chat Completions 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 能够清楚地区分是系统还是用户在说话这有助于它构建对话历史并在此基础上继续回答。步骤 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——属性所存储的数据类型properties——模型将用于其响应的具体取值列表name——键即模型在格式化响应中使用的属性名例如producttype——该属性的数据类型例如stringdescription——对该具体属性的描述。此外还有一个可选属性required用于标记完成函数调用所必需的参数。上例中只有role是必填的product与level可以由模型从用户消息中推断、缺失时留空。步骤 3发起函数调用定义函数之后需要把它纳入 Chat Completions API 的请求中方式是插入functions属性即functionsfunctions。还可以把function_call设置为auto。这意味着由 LLM 根据用户消息自行决定调用哪个函数而不是由我们显式指定。下面的代码调用ChatCompletion.create方法注意functionsfunctions与function_callauto的组合让 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属性中。结论是LLM 成功为函数参数找到了数据因为它从聊天补全调用中messages参数提供的值里提取了这些信息。回顾一下messages的值messages [ {role: user, content: Find me a good course for a beginner student to learn Azure.} ]可见student、Azure与beginner是从messages中提取出来、作为函数输入设置的。以这种方式使用函数既是从提示词中抽取信息的好方法也能为 LLM 提供结构、并建立可复用的功能。把函数调用集成到应用中测试完 LLM 的格式化响应后就可以把它集成到应用里了。管理整体流程集成到应用需要按以下步骤进行首先调用 OpenAI 服务并把消息存入response_message变量response_message response.choices[0].message然后定义一个真正调用 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变量和对应的 Python 函数如何告诉 LLM 把两者映射起来使我们的 Python 函数被调用要判断是否需要调用 Python 函数需要检查 LLM 响应中是否包含function_call如果包含就调用它指向的函数。检查与分发逻辑如下# 检查模型是否想调用函数 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)) # 把助手响应与函数响应追加到消息中 messages.append( # 把助手响应追加到消息中 { 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)运行代码后的输出如下输出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最后把更新后的messages再次发送给 LLM以拿到自然语言响应而不是 API 的 JSON 格式响应print(Messages in next request:) print(messages) print() second_response client.chat.completions.create( messagesmessages, modeldeployment, function_callauto, functionsfunctions, temperature0 ) # 从 GPT 获取一个新响应此时它能看到函数的响应 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. }这里把temperature设为0是为了让最终的自然语言总结尽可能确定、稳定。整个流程至此闭环messages→ 模型返回function_call→ 应用执行 Python 函数 → 函数名、参数与结果被追加回messages→ 模型基于完整上下文输出面向用户的回答。仓库源码中的同源实现与工程化细节课程文档之外仓库在同一目录下提供了多种语言的配套示例可以印证上述调用链并补充文档没有展开的工程化细节。TypeScript 示例把安全与健壮性落到实处11-integrating-with-function-calling/typescript/function-app/src/main.ts 是一个“查询天气”的函数调用应用用 Bing Maps 作为外部数据源。从源码结构看它比文档中的教学代码多了几类值得借鉴的处理环境变量与 URL 校验启动时校验AZURE_OPENAI_ENDPOINT、AZURE_OPENAI_API_KEY、BING_MAPS_BASE_URL、BING_API_KEY是否齐全main.ts 第 833 行并要求端点必须是 HTTPS 格式扁平化的函数tools定义getCurrentWeatherTool以{ type: function, name, description, parameters }的扁平结构声明findWeather参数中的unit字段使用了enum: [C, F]约束取值第 6078 行这是对文档中parameters说明的补充——枚举约束可以让模型输出更可控安全的参数解析对模型返回的arguments使用try/catch包裹JSON.parse解析失败或缺少必填的location时记录错误并跳过而不是让进程崩溃第 112127 行带超时的外部调用findWeather中axios.get(url, { timeout: 10000 })防止请求挂起并在出错时返回{ error: Failed to retrieve weather data }避免把含敏感信息的完整错误对象直接打出来第 4456 行。该示例的运行方式由 11-integrating-with-function-calling/typescript/function-app/package.json 中的nodemonConfig给出监听src目录执行npx ts-node ./src/main.ts。JavaScript 示例多函数白名单分发11-integrating-with-function-calling/js-githubmodels/app.js 演示了“一个请求携带两个工具”的场景getFlightInfo与getHotelInfo两个工具同时传给模型由模型根据用户意图选择其一。它体现了三个关键实践白名单分发与文档中 Python 的available_functions字典同理这里维护了namesToFunctions映射并在调用前用Object.prototype.hasOwnProperty.call(namesToFunctions, functionName)校验模型请求的函数名是否在白名单内未知函数名直接抛出异常第 141145 行——这是防止模型“幻觉”出不存在函数的重要防线完成原因判断通过response.body.choices[0].finish_reason tool_calls判断模型是否要求调用工具第 128 行结果回传格式函数结果以{ tool_call_id, role: tool, name, content }的形式追加到消息历史后再发起第二次请求获得自然语言回答第 162178 行。与英文版第 11 课的接口差异需要注意的是仓库中的英文版 11-integrating-with-function-calling/README.md 已把同一套流程更新到了 OpenAI Responses API函数列表改以tools传递、选择策略改为tool_choiceauto、请求与回传分别使用client.responses.create(..., storeFalse)与function_call_output消息项。从源码结构看本仓库的匈牙利语文档及配套笔记本仍采用经典的chat.completions.create加functions/function_call接口。两套写法背后的设计思想完全一致——“声明结构 → 模型产出结构化调用 → 应用执行 → 结果回传 → 二次生成”——只是 API 表面发生了变化。迁移时可以把本文的流程步骤逐条对应过去。课后任务文档最后给出了三个可动手的扩展方向用于继续深化对函数调用的理解为函数增加更多参数帮助学习者找到更多课程创建另一个函数调用向学习者收集更多信息例如母语为函数调用和/或 API 调用没有返回合适课程的情形编写错误处理。实现时可以对照 Microsoft Learn 的 Catalog API 开发者参考文档了解这些数据的可用参数与访问方式仓库中的 TypeScript 与 JavaScript 示例见上文“仓库源码中的同源实现”一节也提供了参数校验、白名单分发与异常处理的现成参照。小结本文完整继承了课程文档“问题演示 → 三要素消息、函数定义、function_callauto调用→ 应用集成四步流程”的主线并用仓库内 TypeScript 天气应用、JavaScript 多工具示例 与 匈牙利语配套笔记本 佐证了该模式的可移植性与工程化要点函数名白名单、参数安全解析、超时与错误兜底。掌握这条“自然语言进、结构化参数中、自然语言出”的链路后你就能为任意应用接入实时数据与外部工具能力。后续可继续阅读 匈牙利语第 12 课为 AI 应用设计 UX学习如何为这类带工具调用的应用设计用户体验。【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表