ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 14px;color: rgb(63, 63, 63);" class="list-paddingleft-1"> ingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 14px;color: rgb(63, 63, 63);">
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;display: table;padding: 0px 1em;color: rgb(63, 63, 63);">初次上手如何快速开发基于大模型+function call的Agent应用?
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">最近主导了一款ai agent系统的开发,在定架构的时候选择了MCP协议,在期间遇到不少坑点,记录顺分享一下相关内容。此篇是系列的第四篇:读完预计能对怎么快速搭建一个基于大模型和function call能力下的agent应用有初步的认知~
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">此前介绍过基于MCP协议的开发内容(当时介绍中开发细节较少),实则可以不基于MCP,仅基于大模型+function call能力就能开发一款agent应用。此篇重点记录下小白在开发过程中可能产生的一些问题和疑问的解答(含代码~),部分内容如messages该如何组织适配agent应用因篇幅原因会在下一文联合上下文处理一起介绍。 ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;display: table;padding: 0px 0.2em;color: rgb(255, 255, 255);background: rgb(15, 76, 129);">what?——function call是什么?
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">这个问题在前一篇文章中有讲述过,简单说:Function Call就是让智能助手能调用外部工具的功能,比如查天气、订外卖、算数学题,让它从"只会说话"变成"会办实事"的全能帮手! ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;display: table;padding: 0px 0.2em;color: rgb(255, 255, 255);background: rgb(15, 76, 129);">why?——为什么需要function call?
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">大模型本身的一些缺陷,例如:
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;letter-spacing: 0.1em;color: rgb(63, 63, 63);">1. 知识有时效性缺陷 比如你问 "2025 年 NBA 总冠军是谁?"(假设现在是 2025 年),模型如果没学过 2025 年的新数据,就答不上来。但用 Function Call 调用体育新闻 API,马上能拿到实时结果。
2. 不会做专业操作
模型懂医学知识,但不能直接帮你查医院排班;懂数学公式,但不会用 Excel 算工资表。而 Function Call 能喊医院系统、办公软件来干活,实现 "理论 + 实操" 结合。
how?——怎么做一个基于function call的应用? 整个系统框架可以如下图所示:
1)用户发起请求 (User→应用系统):用户向应用系统提交自然语言问题(如"查询上海天气")。
2)提示词编排 (应用系统→LLM):应用系统将问题转化为结构化提示词,调用大模型并请求决策。
3)工具调用判断 (LLM决策分支):
无需工具 :LLM直接生成文本回复(如回答常识问题)。
需工具调用 :LLM输出工具名称及参数(如WeatherAPI: {location: "上海"})。
4)安全审批 (应用系统→Host系统):工具调用需经权限校验与风险审核,通过后触发执行确认。
5)函数执行 (Host系统):Host系统按参数调用目标工具(如天气API),获取原始结果(如JSON数据)。
6-8)结果整合 (逆向传递链):
工具结果经Host系统→应用系统→LLM逐层返回,LLM将数据转化为自然语言(如"上海今日多云,28°C")。
9)最终响应 (应用系统→User):应用系统将LLM生成的友好回复返回给用户,流程终止。
why&how?——小白开发中可能有的几个核心疑问 怎么实现函数调度的功能? 整体其实就是提示词来激发模型选择工具的能力,目前开发过程中主要有两种实现方式:一种是直接使用大模型API提供方提供的利用tools和messages的组织方式(其实背后他们自己也是通过prompt来组建的一套能力,不过相比自己开发一套提示词工程而言,他们更了解底层模型如何做的,在通用化场景下的适配效果可能比自己研究要好很多。
阿里云官网提供的函数调用的流程图解
1)使用官方提供的服务+openai接口访问方式:快速使用推荐!
简版代码如下,但提前需准备好tools的定义(见下文tools的样式小节)和messages数组 (见下文messages该如何组织部分,后续推文会有更详细的介绍)。
fromopenaiimportOpenAI importos client = OpenAI( # 若没有配置环境变量,请用提供商提供的api_key="sk-xxx", api_key=os.getenv("API_KEY"), base_url="BASE_URL", ) deffunction_calling(): completion = client.chat.completions.create( model="qwen-plus", # 此处以qwen-plus为例 messages=messages, tools=tools# 这里tools的结构体可以见下文tools的样式 ) print("返回对象:") print(completion.choices[0].message.model_dump_json()) print("\n") returncompletion print("正在发起function calling...") completion = function_calling()2)prompt+系统自实现:完全小白不建议用这个哈,相对自适配能力更强,但很考验对底层模型的理解。
上图提供了函数调用流程的图解,实际自己用prompt开发流程与上图一致。用户提问的时候将工具内容和问题组织为一定的提示词给到大模型,然后写一段拆解函数拆解大模型返回内容里面携带的工具调用信息和其他文本内容。下面是一个简单的访问模型的例子,注意拆解工具调用函数部分并没有给出。
importos fromopenaiimportOpenAI importjson client = OpenAI( # 若没有配置环境变量,请用提供商提供的api_key="sk-xxx", api_key=os.getenv("API_KEY"), base_url="BASE_URL", ) # 自定义 System prompt,可根据您的需求修改 custom_prompt ="你是一个智能助手,专门负责调用各种工具来帮助用户解决问题。你可以根据用户的需求选择合适的工具并正确调用它们。" tools = [ # 工具1 获取当前时刻的时间 { "type":"function", "function": { "name":"get_current_time", "description":"当你想知道现在的时间时非常有用。", "parameters": {} } }, # 工具2 获取指定城市的天气 { "type":"function", "function": { "name":"get_current_weather", "description":"当你想查询指定城市的天气时非常有用。", "parameters": { "type":"object", "properties": { "location": { "type":"string", "description":"城市或县区,比如北京市、杭州市、余杭区等。" } }, "required": ["location"] } } } ] # 遍历tools列表,为每个工具构建描述 tools_descriptions = [] fortoolintools: tool_json = json.dumps(tool, ensure_ascii=False) tools_descriptions.append(tool_json) # 将所有工具描述组合成一个字符串 tools_content ="\n".join(tools_descriptions) system_prompt =f"""{custom_prompt} # Tools You may call one or more functions to assist with the user query. You are provided with function signatures within <tools></tools> XML tags: <tools> {tools_content} </tools> For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags: <tool_call> {{"name": <function-name>, "arguments": <args-json-object>}} </tool_call>""" messages = [ {"role":"system","content": system_prompt}, {"role":"user","content":"几点了"} ] completion = client.chat.completions.create( model="qwen-plus", messages=messages, ) print(completion.model_dump_json())tools的样式是什么样的?怎么准备tools? OpenAI协议中,tools目前通用格式如下:
tools = [ { "type":"function", "function": { "name":"get_current_time", "description":"当你想知道现在的时间时非常有用。", } }, { "type":"function", "function": { "name":"get_current_weather", "description":"当你想查询指定城市的天气时非常有用。", "parameters": { "type":"object", "properties": { "location": { "type":"string", "description":"城市或县区,比如北京市、杭州市、余杭区等。", } }, "required": ["location"] } } } ] tool_name = [tool["function"]["name"]fortoolintools] print(f"创建了{len(tools)}个工具,为:{tool_name}\n")其中,
•name字段为自定义的工具函数名称,建议使用与函数相同的名称,如get_current_weather或get_current_time; •description字段是对工具函数功能的描述,大模型会参考该字段来选择是否使用该工具函数。 •parameters字段是对工具函数入参的描述,类型是 Object ,大模型会参考该字段来进行入参的提取。如果工具函数不需要输入参数,则无需指定parameters参数。 •properties字段描述了入参的名称、数据类型与描述,为 Object 类型,Key 值为入参的名称,Value 值为入参的数据类型与描述; •required字段指定哪些参数为必填项,为 Array 类型。 messages该如何组织? message数组样例如下,具体role有 system、user、tool、assistant。
• system:对话的全局导演,设定模型的行为框架和初始规则。交互框架的 “设计者” 与 “规则制定者”。 • user:交互的 “需求发起者” 与 “信息输入方”。 • tool:交互中的 “功能执行者” 与 “数据提供者”。 • assistant:交互的 “问题解决者” 与 “内容整合者”。 messages = [ { "role":"system", "content":"""你是一个很有帮助的助手。如果用户提问关于天气的问题,请调用 ‘get_current_weather’ 函数; 如果用户提问关于时间的问题,请调用‘get_current_time’函数。 请以友好的语气回答问题。""", }, { "role":"user", "content":"深圳天气" } ] print("messages 数组创建完成\n")怎么进行函数的并行调度? 部分问题可能需要同时调用多个工具,比如问北京和上海的天气如何?这个问题可能需要调用两次天气函数分别查询北京和上海的天气情况。
deffunction_calling(): completion = client.chat.completions.create( model="qwen-plus",# 此处以 qwen-plus 为例,可按需更换模型名称 messages=messages, tools=tools, # 新增参数 parallel_tool_calls=True ) print("返回对象:") print(completion.choices[0].message.model_dump_json()) print("\n") returncompletion print("正在发起function calling...") completion = function_calling()怎么强制使用或者不使用工具? tool_choice可以选择:auto,none,required(必须调用至少一个工具)或者指定要调用的函数
# 强制使用某个工具 deffunction_calling(): completion = client.chat.completions.create( model="qwen-plus", messages=messages, tools=tools, tool_choice={"type":"function","function": {"name":"get_current_weather"}} ) print(completion.model_dump_json()) function_calling() # 强制不使用工具:tool_choice设置为none deffunction_calling(): completion = client.chat.completions.create( model="gpt-4", messages=messages, tools=tools, tool_choice="none" ) print(completion.model_dump_json()) function_calling()完整的代码参考 fromopenaiimportOpenAI fromdatetimeimportdatetime importjson importos importrandom client = OpenAI( # 若没有配置环境变量,请用百炼API Key将下行替换为:api_key="sk-xxx", api_key=os.getenv("DASHSCOPE_API_KEY"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", # 填写DashScope SDK的base_url ) # 定义工具列表,模型在选择使用哪个工具时会参考工具的name和description tools = [ # 工具1 获取当前时刻的时间 { "type":"function", "function": { "name":"get_current_time", "description":"当你想知道现在的时间时非常有用。", # 因为获取当前时间无需输入参数,因此parameters为空字典 "parameters": {}, }, }, # 工具2 获取指定城市的天气 { "type":"function", "function": { "name":"get_current_weather", "description":"当你想查询指定城市的天气时非常有用。", "parameters": { "type":"object", "properties": { # 查询天气时需要提供位置,因此参数设置为location "location": { "type":"string", "description":"城市或县区,比如北京市、杭州市、余杭区等。", } }, "required": ["location"], }, }, }, ] # 模拟天气查询工具。返回结果示例:“北京今天是雨天。” defget_current_weather(arguments): # 定义备选的天气条件列表 weather_conditions = ["晴天","多云","雨天"] # 随机选择一个天气条件 random_weather = random.choice(weather_conditions) # 从 JSON 中提取位置信息 location = arguments["location"] # 返回格式化的天气信息 returnf"{location}今天是{random_weather}。" # 查询当前时间的工具。返回结果示例:“当前时间:2024-04-15 17:15:18。“ defget_current_time(): # 获取当前日期和时间 current_datetime = datetime.now() # 格式化当前日期和时间 formatted_time = current_datetime.strftime("%Y-%m-%d %H:%M:%S") # 返回格式化后的当前时间 returnf"当前时间:{formatted_time}。" # 封装模型响应函数 defget_response(messages): completion = client.chat.completions.create( model="qwen-plus", # 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models messages=messages, tools=tools, ) returncompletion defcall_with_messages(): print("\n") messages = [ { "content":input( "请输入:" ), # 提问示例:"现在几点了?" "一个小时后几点" "北京天气如何?" "role":"user", } ] print("-"*60) # 模型的第一轮调用 i =1 first_response = get_response(messages) assistant_output = first_response.choices[0].message print(f"\n第{i}轮大模型输出信息:{first_response}\n") ifassistant_output.contentisNone: assistant_output.content ="" messages.append(assistant_output) # 如果不需要调用工具,则直接返回最终答案 if( assistant_output.tool_calls ==None ): # 如果模型判断无需调用工具,则将assistant的回复直接打印出来,无需进行模型的第二轮调用 print(f"无需调用工具,我可以直接回复:{assistant_output.content}") return # 如果需要调用工具,则进行模型的多轮调用,直到模型判断无需调用工具 whileassistant_output.tool_calls !=None: # 如果判断需要调用查询天气工具,则运行查询天气工具 tool_info = { "content":"", "role":"tool", "tool_call_id": assistant_output.tool_calls[0].id, } ifassistant_output.tool_calls[0].function.name =="get_current_weather": # 提取位置参数信息 argumens = json.loads(assistant_output.tool_calls[0].function.arguments) tool_info["content"] = get_current_weather(argumens) # 如果判断需要调用查询时间工具,则运行查询时间工具 elifassistant_output.tool_calls[0].function.name =="get_current_time": tool_info["content"] = get_current_time() tool_output = tool_info["content"] print(f"工具输出信息:{tool_output}\n") print("-"*60) messages.append(tool_info) assistant_output = get_response(messages).choices[0].message ifassistant_output.contentisNone: assistant_output.content ="" messages.append(assistant_output) i +=1 print(f"第{i}轮大模型输出信息:{assistant_output}\n") print(f"最终答案:{assistant_output.content}") if__name__ =="__main__": call_with_messages()