generative-ai-for-beginners 第11课深度解析:用 Function Calling 让 LLM 以结构化数据调用真实外部服务
2026/9/10 5:22:22 网站建设 项目流程

generative-ai-for-beginners 第11课深度解析:用 Function Calling 让 LLM 以结构化数据调用真实外部服务

【免费下载链接】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)的核心概念、三步创建流程与应用集成方案,并结合仓库中的 Jupyter 练习、TypeScript 与 JavaScript 示例代码,讲清 LLM 如何在聊天场景中提取结构化参数、映射并执行真实 API 调用,最终把结果用自然语言返回给用户。

引言与学习目标

在掌握提示词工程、文本生成与聊天应用之后,本课要解决两个更实际的问题:如何让 LLM 的响应格式更稳定,便于下游程序处理;以及如何把外部数据源引入聊天上下文,丰富应用能力。这正是函数调用要解决的痛点。

本课(希伯来语版本,英文原版见 11-integrating-with-function-calling/README.md)覆盖以下内容:

  • 解释什么是函数调用(Function Calling)及其典型用途;
  • 使用 Azure OpenAI 创建一次函数调用;
  • 将函数调用集成到实际应用中。

学完本课,你将能够:

  • 说清楚使用函数调用的目的与价值;
  • 使用 Azure OpenAI 服务完成函数调用的配置;
  • 针对自己的应用场景设计出有效的函数调用。

场景:用函数改进教育聊天机器人

本课的实战场景是为一家教育创业公司开发功能:用户通过聊天机器人查找技术课程,系统根据其技能水平、当前角色、感兴趣的技术推荐合适的课程。

完成该场景需要组合使用三种能力:

组件作用
Azure OpenAI为用户提供聊天体验
Microsoft Learn Catalog API根据用户请求检索匹配的课程
Function Calling接收用户查询并交给函数去执行 API 请求

为什么需要函数调用

在函数调用出现之前,LLM 的响应是非结构化且不稳定的。开发者必须编写复杂的校验代码,才能应对响应的每一种变体;用户也无法问出“斯德哥尔摩现在的天气如何?”这类问题——因为模型的知识受限于训练数据的时间范围。

函数调用是 Azure OpenAI 服务的一项能力,专门用来克服以下两类局限:

  • 一致的响应格式:更好地控制响应格式后,响应才能被轻松地集成到其他系统;
  • 外部数据:在聊天上下文中使用来自应用其他数据源的数据。

用场景演示格式问题:同一提示词,两种输出

如果你希望实际运行下面的场景,仓库为希伯来语版配套了可运行的笔记本:aoai-assignment.ipynb(英文版笔记本见 11-integrating-with-function-calling/python/aoai-assignment.ipynb)。也可以只阅读代码,因为我们的目标是演示一个“函数可以帮忙解决问题”的典型问题。

假设我们要建立一个学生数据数据库,以便为他们推荐合适的课程。下面两条学生描述在数据结构上非常相似:

1. 创建到 Azure OpenAI 资源的连接

import os import json from openai import AzureOpenAI from dotenv import load_dotenv load_dotenv() client = AzureOpenAI( api_key=os.environ['AZURE_OPENAI_API_KEY'], # 这也是默认值,可以省略 api_version = "2023-07-01-preview" ) deployment=os.environ['AZURE_OPENAI_DEPLOYMENT']

这段 Python 代码配置了与 Azure OpenAI 的连接,需要设置api_keyapi_version,并通过环境变量AZURE_OPENAI_DEPLOYMENT指定部署名(即后续调用时的model参数值)。

2. 创建两条学生描述

student_1_description="Emily Johnson is a sophomore majoring in computer science at Duke University. She has a 3.7 GPA. Emily is an active member of the university's 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 university's Robotics Club. He hopes to pursue a career in artificial intelligence after finishing his studies."

我们希望把这两段描述发给 LLM 解析其中的数据,这些数据之后可以用在应用里,发给某个 API 或存入数据库。

3. 创建两条完全相同的提示词

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 格式返回响应。

4. 发送请求并解析响应

# response from prompt one openai_response1 = client.chat.completions.create( model=deployment, messages = [{'role': 'user', 'content': prompt1}] ) openai_response1.choices[0].message.content # response from prompt two openai_response2 = client.chat.completions.create( model=deployment, messages = [{'role': 'user', 'content': prompt2}] ) openai_response2.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.8 GPA。原因是 LLM 接收的是提示词这种非结构化数据,返回的也是非结构化数据。而我们需要一个结构化格式,才能在存储或使用这些数据时明确预期。

用函数调用解决格式问题

借助函数调用,我们可以确保拿到结构化的数据返回。需要强调一个关键机制:使用函数调用时,LLM 实际上并不会真正调用或执行任何函数。相反,我们为 LLM 创建了一个它必须遵循的响应结构;然后我们在应用端利用这些结构化响应,来决定真正执行哪个函数。

拿到函数返回的结果后,可以把它再次送回 LLM,由 LLM 用自然语言回答用户的问题。

函数调用的典型用例

函数调用可以在很多场景下改善应用:

  • 调用外部工具。聊天机器人擅长回答用户问题,借助函数调用,机器人还能根据用户消息执行具体任务。例如学生可以对聊天机器人说“给我的导师发一封邮件,说明我需要在这个主题上获得更多帮助”,这可以触发一次send_email(to: string, body: string)函数调用。
  • 生成 API 或数据库查询。用户可以用自然语言查找信息,再被转换为格式化的查询或 API 请求。例如老师问“哪些学生完成了上次作业”,可以调用名为get_completed(student_name: string, assignment: int, current_status: string)的函数。
  • 生成结构化数据。用户可以给 LLM 一段文本或 CSV,让它提取重要信息。例如学生可以把一篇关于和平协议的维基条目转成 AI 知识卡片,通过get_important_facts(agreement_name: string, date_signed: string, parties_involved: list)函数完成。

创建第一次函数调用:三步流程

创建一次函数调用包含三个主要步骤:

  1. 调用Chat Completions API,携带你的函数列表和一条用户消息;
  2. 读取模型的响应并执行动作,即运行某个函数或发起 API 调用;
  3. 再次调用Chat Completions API,把函数返回的结果交给模型,用于生成面向用户的最终响应。

步骤 1:创建消息(messages)

第一步是创建一条用户消息。你可以动态赋值(例如取自文本输入框的值),也可以直接写死。如果是第一次接触 Chat Completions API,需要定义消息的rolecontent

role可以是system(制定规则)、assistant(模型)或user(最终用户)。对函数调用而言,我们把它指定为user并给一个示例问题:

messages= [ {"role": "user", "content": "Find me a good course for a beginner student to learn Azure."} ]

通过分配不同的角色,LLM 能分清这是系统在说话还是用户在说话,这有助于构建一段模型可以持续往上叠加的对话历史。

步骤 2:定义函数(functions)

接下来定义函数及其参数。这里只用一个名为search_courses的函数,但你可以创建多个:

重要:函数定义会包含在发给 LLM 的系统消息里,因此会占用你可用的 token 额度。

函数被组织成一个数组,每个元素是一个函数,包含namedescriptionparameters属性:

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—— 你希望模型在响应中产出的取值与格式列表,由若干条目组成,每个条目包含:
    1. type—— 属性存储的数据类型;
    2. properties—— 模型在结构化响应中将使用的具体字段列表,每个字段含:
      • 键名为该字段的名称,例如product
      • type—— 该字段的数据类型,例如string
      • description—— 该字段的描述。

此外还有一个可选属性required:完成这次函数调用所必需的字段。上面search_courses只把role标为必需,意味着模型在无法确定产品与级别时仍可以发起调用。

步骤 3:发起函数调用

定义好函数后,需要把它包含进 Chat Completions API 的请求中,即添加functions=functions。还可以把function_call设为auto,表示让 LLM 根据用户消息自行决定调用哪个函数,而不是由我们手动指定。

response = client.chat.completions.create(model=deployment, messages=messages, functions=functions, function_call="auto") 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属性中。回顾一下messages的值:

messages= [ {"role": "user", "content": "Find me a good course for a beginner student to learn Azure."} ]

可以清楚看到,studentAzurebeginner是从用户消息中提取出来并作为函数输入设置的。这样使用函数既是从提示词中提取信息的好方法,也给 LLM 提供了结构约束,从而获得可复用的功能。

把函数调用集成到应用里

测试好格式化响应后,就可以把它集成进应用了。

管理调用流程

第一步,执行对 OpenAI 服务的调用,并保存返回的消息:

response_message = response.choices[0].message

第二步,定义真正执行 API 调用的 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, params=params) 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 检索培训模块)。

那么如何告诉 LLM 把两者映射起来、让 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)

运行后的输出(节选):

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_id=api_CatalogApi'}, {'title': 'Introduction to audio classification with TensorFlow', 'url': '...'}, {'title': 'Design a Performant Data Model in Azure SQL Database with Azure Data Studio', 'url': '...'}, {'title': 'Getting started with the Microsoft Cloud Adoption Framework for Azure', 'url': '...'}, {'title': 'Set up the Rust development environment', 'url': '...'}] <class 'str'>

第四步,把更新后的messages(其中已追加助手消息与role: "function"的函数结果消息)再次发给 LLM,就能收到自然语言响应,而不是 API 的 JSON 响应:

print("Messages in next request:") print(messages) print() second_response = client.chat.completions.create( messages=messages, model=deployment, function_call="auto", functions=functions, temperature=0 ) # get a new response from GPT where it can see the function response 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_id=api_CatalogApi)\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." }

至此,完整闭环形成:用户自然语言 → LLM 输出结构化函数调用 → 应用执行真实 API → 结果回填对话 → LLM 用自然语言总结回答。

仓库源码对照:本课示例在其他语言与新版 API 中的形态

本课希伯来语文档采用的是AzureOpenAI客户端加functions/function_call="auto"的经典 Chat Completions 写法。从仓库当前代码看,同一目录下的实践示例已经演进到Responses APIclient.responses.create+tools+tool_choice="auto")格式,核心思想不变,值得对照阅读:

  • Python 练习笔记本(11-integrating-with-function-calling/python/aoai-assignment.ipynb):函数定义改为扁平化的工具格式,顶层带"type": "function";调用时传tools=functionstool_choice="auto"store=False。响应中会包含typefunction_call的输出项(带call_idarguments),执行函数后把{"type": "function_call_output", "call_id": ..., "output": ...}追加回消息列表,再第二次调用模型得到自然语言答案。多函数场景下用列表推导tool_calls = [item for item in response.output if item.type == "function_call"]统一提取。
  • TypeScript 天气示例(11-integrating-with-function-calling/typescript/function-app/src/main.ts):演示了把函数调用接到 Bing Maps 天气 API 的完整链路,并附带不少工程化细节——启动时校验AZURE_OPENAI_ENDPOINTAZURE_OPENAI_API_KEYBING_MAPS_BASE_URLBING_API_KEY四个环境变量,强制端点必须为 HTTPS,为外部请求设置 10 秒超时,对函数参数做 JSON 解析与必填项校验;其工具定义中还使用了enum: ["C", "F"]来约束温度单位取值,这是“用 parameters 约束输出”思路的落地示例。
  • JavaScript 差旅示例(11-integrating-with-function-calling/js-githubmodels/app.js):演示多函数场景——同时注册getFlightInfogetHotelInfo两个工具,并用namesToFunctions这样的名称映射表把模型选择的函数名路由到本地实现,与上文available_functions字典是同一模式。

这些示例说明,无论 API 形态如何演进,“声明结构 → 模型提取参数 → 本地映射执行 → 结果回填对话”这条主链路都是函数调用的标准骨架。

课后任务

为了继续巩固 Azure OpenAI 函数调用,可以尝试:

  • 为函数增加更多参数,帮助学习者找到更多课程;
  • 再创建一次函数调用,采集学习者更多信息,例如其母语;
  • 当函数调用和/或 API 调用没有返回合适课程时,加入错误处理。

提示:可以查阅 Learn API 的参考文档,了解这些数据的可用字段与位置(检索 Microsoft Learn Catalog API developer reference 即可)。

小结

本课从“响应格式不稳定”这一真实痛点出发,建立了函数调用的完整认知框架:

  1. 原理:LLM 并不执行函数,而是按你声明的结构产出参数,由应用端真正执行;
  2. 定义:用name/description/parameters(含typepropertiesrequired)描述函数契约,description的清晰程度直接决定调用质量,同时要注意函数定义占用 token 预算;
  3. 流程messages传入用户意图 →function_call="auto"让模型自选函数 → 解析arguments并映射到本地函数执行 → 把role: "function"的结果消息回填对话 → 第二次调用模型得到自然语言答案。

下一篇进入第 12 课,讨论如何为 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询