news 2026/9/8 19:28:04

Generative AI for Beginners:第 11 课实战解析——用 Azure OpenAI Function Calling 为教育推荐聊天机器人接入外部课程数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Generative AI for Beginners:第 11 课实战解析——用 Azure OpenAI Function Calling 为教育推荐聊天机器人接入外部课程数据

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

本篇文章以开源课程仓库 generative-ai-for-beginners(21 Lessons, Get Started Building with Generative AI)第 11 课《与函数调用集成(Integrating with function calling)》为基础,系统讲解 Azure OpenAI Function Calling(函数调用)的动机、原理与端到端集成方法。你将掌握:函数调用如何解决 LLM 输出格式不稳定与"无法访问外部数据"两大痛点、如何用 JSON Schema 声明函数并以auto模式让模型自主选择函数与参数、以及如何在真实应用中通过"第二次补全请求"把外部 API 结果转成自然语言推荐。文中所有示例均可结合本仓库的 Notebook、Python、JavaScript 与 TypeScript 配套代码动手运行。

课程场景:让教育类聊天机器人具备"找课程"能力

第 11 课面向课程中反复出现的一家教育初创公司,目标是为用户提供一个聊天机器人,它能根据用户的技能水平(skill level)、**当前角色(current role)感兴趣的技�术(technology of interest)**推荐合适的 IT 课程。为实现这一场景,该课组合使用了三类组件:

  • Azure OpenAI:为用户提供对话体验;
  • Microsoft Learn Catalog API:根据用户请求帮助其查找课程(训练模块);
  • Function Calling:把用户查询转化为函数调用参数,由函数代替用户发起真实的外部 API 请求。

要理解为什么要引入函数调用,必须先看没有它时的两个根本性限制。

为什么需要函数调用:一致格式与外部数据

在函数调用能力出现之前,LLM 存在两个明显短板:

  • 响应非结构化且不稳定:开发者必须编写复杂的校验代码,才能兜住模型输出的每一种格式变体;
  • 模型知识受训练时间点限制:模型无法回答"斯德哥尔摩现在天气如何"这类需要实时数据的问题,因为其内部参数只固化到训练数据截止的某一时刻。

Function Calling 正是 Azure OpenAI 服务为克服上述限制提供的能力,它带来两个核心收益:

  • 一致的响应格式(Consistent response format):能更好地控制输出结构,从而把模型响应更平滑地集成进下游系统;
  • 外部数据接入(External data):可以在对话上下文中使用应用其他来源的数据。

需要特别强调:函数调用并不是让 LLM 自己去调用或执行某个函数,而是为模型的输出定义一套结构约束;应用拿到这份结构化输出后,才知道该调用自己的哪个函数,真正执行仍然发生在你的程序里。

用"学生信息抽取"场景看懂非结构化输出问题

原文建议直接使用配套 Notebook 运行下面的场景(见 翻译版配套 Notebook 或主仓库 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']

AZURE_OPENAI_API_KEYAZURE_OPENAI_DEPLOYMENT等密钥信息通常放在.env文件中,通过load_dotenv()载入。注意本课翻译版示例运行在 Chat Completions 风格的旧接口之上,主仓库英文版已迁移到 Azure OpenAI Responses API 的 v1 端点(base_url = f"{endpoint.rstrip('/')}/openai/v1/"),两种风格的差异与迁移将在下文"API 风格演进"小节展开。

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. 构造两条内容完全一致的抽取 Prompt

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} '''

Prompt 明确要求模型抽取指定字段并以 JSON 对象返回。

4. 发送请求并读取响应

把 Prompt 放入messages、角色设为user,以模拟用户在聊天机器人中输入的文本:

# 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

通过openai_response1['choices'][0]['message']['content']即可查看模型原始输出。

5. 用 json.loads 解析响应

# 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" }

问题暴露:尽管两条 Prompt 相同、描述文本也高度相似,grades字段的取值却出现3.73.8 GPA两种格式。原因在于 LLM 接收的是提示词这类非结构化输入,返回的同样是非结构化数据;一旦需要落库或交给下游程序,这种不确定性就无法接受。

函数调用如何解决上述问题

函数调用通过为响应提供结构约束来解决问题。流程可概括为下图所示的闭环:

应用拿到函数返回结果后,再把它回传给 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)

创建第一个函数调用:三大步骤

一次完整的函数调用包含三个主要步骤:

  1. 调用(Calling):携带函数列表与用户消息调用 Chat Completions API;
  2. 读取(Reading):读取模型响应以决定执行哪个动作,即执行某个函数或发起一次 API 请求;
  3. 回传(Making):把函数执行结果再次提交给 Chat Completions API,让模型据此生成面向用户的最终回答。

这三步循环的静态结构(用户消息、LLM 引擎、函数声明与参数)可参看下图:

步骤 1:创建消息

第一步是构造一条用户消息。可以从文本框动态取值,也可以直接硬编码。首次使用 Chat Completions API 时,需要明确每条消息的rolecontent

role可以是system(设定规则)、assistant(代表模型)或user(最终用户)。函数调用场景下,用户问题按如下方式放入:

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

为不同消息赋予不同角色,能让 LLM 分清"哪句是系统说的、哪句是用户说的",从而构建起可供模型续写的对话历史。

步骤 2:创建函数(声明 + 参数)

接下来定义函数及其参数。本课只使用一个search_courses函数,实际可以声明多个。

重要提示:这些函数声明会被放入发送给 LLM 的"系统消息"中,因此会占用你可用的 token 额度。函数越多、参数描述越长,消耗的输入 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:参数对象的整体数据类型(此处为object);
    2. properties:模型在输出中会使用的具体字段清单,每个字段又包含:
      • 字段键名(如product):模型在格式化响应中使用的属性名;
      • type:字段数据类型(如string);
      • description:对该字段取值的解释;
  • 另有可选的required数组:声明哪些字段是完成函数调用所必需的。

步骤 3:发起带函数声明的请求,让模型自主选择

定义好函数后,把它通过functions参数挂到请求上,同时设置function_call="auto"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,并在arguments中给出了该函数的实参。回看用户消息:

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

studentAzurebeginner正是从这条消息中抽取出来并填入函数参数的。这种用法既能从提示词中提取结构化信息,也为 LLM 提供了可复用的功能边界。

把函数调用集成进真实应用

验证完模型的结构化响应后,把它接入应用。核心是管理好整个调用流(flow)

第 1 步:保存模型响应消息

response_message = response.choices[0].message

第 2 步:实现与声明对应的真实 Python 函数

接下来编写真正会执行外部 API 请求的 Python 函数。注意:函数名必须与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, 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)

这里对 Microsoft Learn Catalog API 发起真实请求、搜索培训模块,并把前 5 条结果(标题 + 链接)拼成字符串返回。

第 3 步:检查响应、映射函数并执行

函数声明(functions)与 Python 实现函数是两个东西,需要一种映射把它们关联起来。做法是:检查 LLM 响应里是否带有function_call,若有则从"函数名 → Python 函数"的映射字典中取出对应实现并调用:

# 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)

随后把助手侧的函数调用声明role: assistant+function_call)与函数执行结果role: function)都追加进messages,形成完整的多轮上下文。这样第二次请求时模型才能"看到"函数返回的数据。

本课 Python 实现的执行输出大致如下(示例):

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/'}, ...] <class 'str'>

第 4 步:第二次请求,把函数结果变成自然语言推荐

最后把更新后的messages再次发给模型,让它把结构化的课程数据组织成用户可读的自然语言回答:

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\n2. Introduction to audio classification with TensorFlow\n3. ...\n5. Set up the Rust development environment\n\nYou can click on the links to access the courses." }

这里把temperature=0是为了降低二次生成的随机性,让回复更聚焦于函数返回的事实。至此,"用户 → 模型选函数 → 应用执行外部 API → 结果回传 → 模型自然语言作答"的完整闭环就打通了。

仓库配套实现:JavaScript 与 TypeScript 样本中的工程化细节

除本课主示例外,仓库 11-integrating-with-function-calling 还提供了多语言实现,可印证同一套思路在实际工程中的落地方式与安全要求:

  • js-githubmodels/app.js:基于 Azure AI Inference SDK(@azure-rest/ai-inference)调用/chat/completions,声明getFlightInfogetHotelInfo两个"查航班/查酒店"工具,默认模型gpt-4o-mini。它重点演示了两类工程细节:一是通过finish_reason === "tool_calls"判断模型是否请求工具,并把结果以role: "tool"tool_call_id回填上下文;二是安全实践——调用前先用Object.prototype.hasOwnProperty.call(namesToFunctions, functionName)校验函数名是否在白名单内,防止模型幻觉出未注册函数,同时用try/catch包裹JSON.parse以避免解析异常直接中断程序。
  • typescript/function-app/src/main.ts:使用 OpenAI SDK 的client.responses.create(Responses API),声明扁平化的getCurrentWeatherTool{ type, name, description, parameters }),在item.type === "function_call"时解析参数并调用findWeather去访问 Bing Maps 地理编码 API。它还展示了面向外部 API 的更完整防护:校验端点必须为 HTTPS、用URLSearchParams对全部查询参数编码以防注入、为请求设置 10 秒超时、解析失败或缺少必填参数时安全跳过。

这些样本与本课主流程殊途同归,但揭示了"函数名白名单校验、参数解析容错、外部请求超时与编码"这类在把函数调用推向生产时不可或缺的护栏。

API 风格演进:functions 时代与 Responses API 时代的字段对照

需要说明版本适用前提:本课关联的西班牙语翻译版教材及其 Notebook(translations/es)撰写于较早时期,使用的是 Chat Completions 风格的functions/function_call参数;而主仓库的英文版教程(11-integrating-with-function-calling/README.md)、python 目录下的英文 Notebook 以及上述 TypeScript 样本均已迁移到Responses API / Tools 扁平格式。两者的核心思想一致,仅字段命名变化,对照如下:

旧式 Chat Completions(functions)新式 Responses API(tools)说明
client.chat.completions.create(...)client.responses.create(...)入口方法
functions=functionstools=functions携带函数/工具声明
function_call="auto"tool_choice="auto"让模型自主决定是否、调用哪个函数
response_message.function_call.name/.argumentsresponse.outputtype=="function_call"项(name/arguments/call_id读取模型建议的函数与参数
messages.append({"role":"assistant","function_call":{...}})messages.append(tool_call)回填助手侧函数调用项
messages.append({"role":"function","name":...,"content":...})messages.append({"type":"function_call_output","call_id":...,"output":...})回填函数执行结果

无论使用哪一代接口,范式始终不变:模型只负责"决定并产出"结构化调用参数,真正的函数执行与外部数据访问永远发生在应用侧

课后任务(Assignment)

原文在结尾布置了三项进阶练习,用于加深对 Azure OpenAI Function Calling 的理解:

  • 为函数增加更多参数,帮助学习者找到更多合适的课程;
  • 新建一个函数调用,纳入更多学生侧信息(例如其母语 native language);
  • 为"函数调用或 API 调用没有返回任何合适课程"的情况编写错误处理逻辑。

提示:可参考 Learn Catalog API 的官方开发参考文档,确认上述数据在接口中的位置与提供方式。

小结与下一步

至此你已经掌握了函数调用的完整知识闭环:先用"学生信息抽取"实验直观看到非结构化输出的不可靠性,再理解函数调用通过 JSON Schema 约束输出、以auto模式让模型自主选择函数,最后通过"声明函数 → 映射实现 → 回填上下文 → 二次请求"四步把它落地为真实可用的教育课程推荐机器人。本仓库配套 Notebook、Python、JavaScript、TypeScript 多语言样本(11-integrating-with-function-calling)可用于对照运行。

完成本课后,可继续学习课程第 12 课 为 AI 应用设计用户体验,探讨如何把这类具备工具调用能力的 AI 应用做成用户真正愿意使用、可解释、可反馈的产品。

【免费下载链接】generative-ai-for-beginners21 Lessons, Get Started Building with Generative AI项目地址: https://gitcode.com/GitHub_Trending/ge/generative-ai-for-beginners

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:27:41

智能体技术落地四大关键:自进化、世界模型、AI Coding与Agent Infra

看到“2026 奇点智能技术大会”首批议题公布的消息时&#xff0c;我第一反应不是“又一场技术峰会”&#xff0c;而是“终于有人把 Agent 自进化、AI Coding、世界模型、Agent Infra 这四件事放到同一张桌上了”。过去一两年&#xff0c;这几个词分别出现在不同的朋友圈、不同的…

作者头像 李华
网站建设 2026/9/8 19:27:39

OpenClaw 2.0:从开源极客玩具到数字员工平台的架构与实践

OpenClaw 2.0 发布那天&#xff0c;我盯着 GitHub 仓库里那只举着钳子的大龙虾 logo 看了很久。从 0.9 时代就开始用的老用户都清楚&#xff0c;这个项目最早就是个极客玩具——挂在个人博客边上的小机器人&#xff0c;你让它查个天气、记个待办、发条定时推文&#xff0c;就已…

作者头像 李华
网站建设 2026/9/8 19:26:55

WorkBuddy智能工作台实战:从智能体到连接器的自动化指南

1. 这次有奖征集活动&#xff0c;到底在征集什么 先聊一个现象&#xff1a;很多效率工具发布后&#xff0c;用户最容易卡住的不是“装不上”&#xff0c;而是“装好了不知道拿它干什么”。WorkBuddy 这类智能工作台产品尤其如此&#xff0c;它能连接的场景太多&#xff0c;反而…

作者头像 李华
网站建设 2026/9/8 19:25:00

分清MCP与Skill本质,10套MCP服务实战测评

过去三个月&#xff0c;我把 WorkBuddy 当成主力的 MCP 接入试验台&#xff0c;把市面上叫得上名字的 MCP 服务几乎接了个遍。接得越多&#xff0c;越发现一个普遍现象&#xff1a;很多人开口就是“我配了十几个 MCP”&#xff0c;可真要问他“这个流程里哪一段是 Skill、哪一段…

作者头像 李华