掘金 人工智能 前天 10:18
切图仔实现AI接口联网手册
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文分享了如何利用AI接口搭建QQ机器人,解决模型信息不实时的问题,重点介绍了通过Function Call实现联网搜索的方法。文章详细介绍了模型上下文、Function Call的概念、对话检查、调用策略、上下文历史等关键要素,并给出了代码示例和最佳实践,帮助读者理解并构建具备联网搜索能力的AI应用。

🌐 **Function Call 的核心作用**: Function Call 允许AI模型与外部工具和API集成,主要用于识别调用函数的时机并生成函数调用的参数,是实现联网搜索的关键。

⚙️ **定义 Function Tool**: Function Tool 需要定义工具类型、函数名称、描述和参数。参数定义使用JSON Schema格式,包括参数类型、描述、枚举等,帮助AI理解何时调用函数以及所需的参数。

🤔 **对话检查与调用策略**: 对话检查是触发Function Call的关键步骤,通过分析用户消息、评估函数定义和判断是否调用函数来决定。tool_choice字段控制模型的调用策略,一般设置为auto让模型自动识别。

🔄 **重新对话与数据传递**: 当模型决定调用Function Call时,需要通过tool_calls字段返回工具信息,并在新的对话中加入完整的触发信息和本地函数的执行结果,以确保信息传递和流程的正确性。

✅ **执行函数与数据格式**: 执行函数返回的数据需要结构化、完整且可序列化,并包含足够的信息供模型理解和处理。 最佳实践包括使用元数据字段提供上下文信息,并处理接口报错情况。

前言

最近拿AI接口来搭建QQ机器人玩,遇到了模型信息不实时的问题,如今市面大部分AI模型都是自带联网搜索的,遂好奇是怎么实现的,于是把学习的过程记录下来分享一下~

阅读需知:


模型上下文

这个应该算是基本内容了,不过为了后续展开这里还是简单提一下,即:

模型本身是没有记忆的,对模型而言,当调用模型接口时,每一次都是完全新的对话,如果想让模型记住之前的内容,需要把所有的上下文(包括每一次用户提问,以及模型的回答等),都包含在本次对话传递给模型,所以上下文的 token 会不断膨胀

大概会是以下效果:

import OpenAI from "openai"const openai = new OpenAI({   baseURL: 'https://api.deepseek.com',   apiKey: '$yourKey',   defaultHeaders: { 'Content-Type': 'application/json' }, })// 第一次问答openai.chat.completions.create({  messages: [    { role: 'user', content: '你好' }  ]})// 第二次问答openai.chat.completions.create({  messages: [    { role: 'user', content: '你好' },    { role: 'assistant', content: '你好,有什么可以帮你的吗' },    { role: 'user', content: '为什么仿生人会梦到电子羊' },  ]})// 第三次回答...  

Function Call

已知模型没有记忆,需要通过我们传递的上下文的内容来理解要求并输出,如果我们想让模型来进行联网搜索,首先我们要做的,就是要让模型理解 什么时候该去执行联网搜索 这个行为,此时就需要引入 function call 的概念:

它允许语言模型与外部工具和 API 集成,简单理解的话主要功能就两个:

因此,现在需要做的是,定义一个 tool,它的类型大概如下所示:

type FunctionTool = {   // 指定工具类型为"function",区别于其他可能的工具类型   type: "function"   // function对象包含了函数的定义和参数规范   function: {     // 函数的名称,用于调用识别     name: string,         // 可选的函数描述,说明函数的用途和功能     // 这对AI理解何时应该调用此函数非常重要     description?: string,         // 函数的参数定义,使用JSON Schema格式规范     parameters: {       // 指定参数集合为对象类型       type: "object"             // 定义各个参数的属性和验证规则       properties:       {         // 参数名称作为key         [key: string]: {           // 参数的数据类型          type: "string" | "number" | "boolean" | "object" | "array"                     // 可选的参数描述,说明参数的用途、格式或约束           // 这帮助AI理解应提供什么样的值           description?: string                     // 可选的枚举列表,限制参数值必须是指定选项之一           // 例如:["light", "dark", "auto"]表示这三个选项中只能选一个           enum?: string[]                     // 其他可能的JSON Schema字段:           // minimum/maximum: 数值类型的最小/最大值           // minLength/maxLength: 字符串类型的最小/最大长度           // pattern: 字符串必须匹配的正则表达式模式           // format: 特定格式如"email"、"date"、"uri"等           // items: 定义数组元素的模式(当type为"array"时)           // properties: 定义嵌套对象的属性(当type为"object"时)           // required: 嵌套对象中的必需属性(当type为"object"时)         }       },       // 可选的必需参数列表,指定哪些参数是调用函数时必须提供的       // 例如:["query", "count"]表示调用时必须提供这两个参数       required?: string[]     }   }}

对话检查

当定义好了一个 tool 后,附在请求字段中发送给对话模型

const getWeatherTool = {  type: 'function',  function: {    name: 'get_weather',    description: '严格遵守仅当用户问题中询问天气相关问题时触发',    parameters: {      type: 'object',      properties: {},      required: []    }  }}const checkChat = await openai.chat.completions.create({  model: 'deepseek-chat',  messages: [{ role: 'user', content: '你好,今天广州天气如何' }],  tools: [getWeatherTool],  tool_choice: 'auto'})

上面即是一个简单的触发 function call 请求方式,需要了解以下几点概念:


调用策略

tool_choice 字段决定了模型采用策略,一般让它自动识别即可,如果需要强制执行某个 tool,则定义 tool_choice: { type: 'function', function: { name: '$functionName' } }

一次检查函数调用对话,有以下决策过程:

即模型是否决定调用函数,是由多个因素共同作用产生的结果

    function.description
      重要程度:★★★★★作用:提供函数的总体用途和调用时机最佳实践:在这里明确说明何时应该何时不应该调用此函数
description: '仅当用户明确询问天气信息时调用此函数'  
    parameters
      重要程度:★★★★☆作用:通过参数结构暗示调用条件影响:
        参数类型和必需性暗示了输入条件参数描述进一步细化调用场景模型会评估能否从用户输入中提取满足参数要求的值
{    type: "function",    function: {      name: "get_weather",      // 编写严格明确的description    description: "严格限制:仅当用户明确询问天气信息且指定具体地点时才调用。用户必须明确表达查询天气的意图(如'北京天气怎么样'),且必须包含具体地点。对于任何其他类型的问题、评论或无地点指定的天气问询,均不应调用此函数。",      // 设计明智的参数结构    parameters: {        type: "object",        properties: {          location: {            type: "string",            description: "用户明确指定的城市或地区名称"          },          query_intent: {            type: "string",            // 增加枚举参数限定场景          enum: ["weather_query", "not_weather_query"],            description: "判断用户意图是否为天气查询。仅当为'weather_query'时才应调用函数。"          },          has_location: {            // 使用逻辑参数控制调用条件          type: "boolean",            description: "确认用户是否提供了具体地点。必须为true才能调用函数。"          }        },        required: ["location", "query_intent", "has_location"]      }    }  }  
    上下文历史
      重要程度:★★★★☆作用:提供语义线索影响方式:模型会考虑整个对话流程和用户意图
    函数名称
      重要程度:★★★☆☆作用:提供情境理解影响方式:描述性的函数名能帮助模型理解函数用途
name: "get_weather"

上面例子中,假设用户问:"北京今天天气怎么样?",模型大致决策过程如下:

    分析用户消息:识别出"天气"主题和"北京"地点检查可用函数:发现 get_weather 函数阅读函数描述:"仅当用户明确询问天气信息时调用此函数"评估参数结构:需要一个location参数判断能否提供参数:能从用户消息中提取"北京"作为location做出决策:条件满足,决定调用函数

重新对话

一般模型决定调用 function call,那么此次对话的消息有以下特点:

const checkChat = await openai.chat.completions.create({  model: 'deepseek-chat',  messages: [{ role: 'user', content: '你好,今天广州天气如何' }],  tools: [getWeatherTool],  tool_choice: 'auto'})const data = checkChat.choices[0].messageconsole.log(data)// 结构如下所示:{    "role": "assistant",    "content": null,    "tool_calls": [      {        "id": "call_abc123def456",        "type": "function",        "function": {          "name": "get_weather",          // 如果在parameters声明了参数,此次模型会把对应的信息结果塞在这里,如有必要可辅助后续操作        "arguments": "{"location":"北京","query_intent":"weather_query","has_location":true}"        }      }    ]  }

此时已经可以通过模型判断出用户需要进行额外操作的意图,那么只需要根据这个意图,去执行本地的特定方法就行,其流程需要注意以下几点:

const checkChat = await openai.chat.completions.create({  model: 'deepseek-chat',  messages: [{ role: 'user', content: '你好,今天广州天气如何' }],  tools: [getWeatherTool],  tool_choice: 'auto'})const data = checkChat.choices[0].messageif(Array.isArray(data)) {  const toolCall = message.tool_calls[0]    if (toolCall.function.name === 'get_weather') {    // 假设本地有一个自定义的getWeatherForLocation方法    const args = JSON.parse(toolCall.function.arguments)    const location = args.location    const weatherData = await getWeatherForLocation(location)        // 重新开始一个新的对话,带上上下文的同时,带上tool信息    const finalChat = await openai.chat.completions.create({          model: 'deepseek-chat',          messages: [            // 原始用户消息            { role: 'user', content: '你好,今天广州天气如何' },            // 包含工具调用的AI响应            checkChat.choices[0].message,            // 工具执行结果            {               role: 'tool',               tool_call_id: toolCall.id,               content: JSON.stringify(weatherData)            }          ]        })   }}

执行函数

由上面例子可知,最终需要把执行函数的返回内容加入到对话上下文中,虽然返回的格式没有严格的规定,但是有一些最佳实践可以遵循:

例子中的获取天气返回格式可以如下所示

// 简单版本{    "location": "北京",    "temperature": "12°C",    "condition": "晴天",    "date": "2023-11-24"  }  
// 复杂版本{    "location": "北京",    "current": {      "temperature": 12,      "temperature_unit": "celsius",      "condition": "晴",      "humidity": 45,      "wind": {        "speed": 10,        "direction": "东北",        "unit": "km/h"      }    },    "forecast": [      {        "date": "2023-11-25",        "day_of_week": "星期六",        "condition": "多云",        "temperature": {          "max": 14,          "min": 5,          "unit": "celsius"        },        "chance_of_rain": "20%"      }      // 可以包含更多天的预报    ],    "metadata": {      "last_updated": "2023-11-24T14:30:00+08:00",      "data_source": "OpenWeather API"    }  }

metadata 字段是一种包含 关于数据的数据 的常见模式,它提供了上下文信息,而不是核心数据本身,其字段名称不是一个严格规定的固定标准,而是一种广泛采用的习惯性约定,其可能功能如下:

    对话连贯性

      AI可以利用最后更新时间生成更自然的回复:

        "北京的天气数据是30分钟前更新的,现在温度12°C"

    可能的后续问题处理

      用户:"这个数据可靠吗?"AI可以引用元数据:"数据来自OpenWeather API,这是一个知名的天气数据提供商"

    限制澄清

      当数据较旧时自动提供说明:

        "请注意,这是6小时前的数据,实际天气状况可能已有变化"

同时,还应考虑到接口报错的情况:

// 接口错误时传递的格式{    "error": true,    "message": "无法获取北京的天气信息",    "reason": "API服务不可用"  }  

联网功能

看到这里,大伙应该已经知道该怎么让 AI 联网搜索了,总得思路就一条:

用户输入提问用提示词规定模型判断是否需要联网搜索需要联网时触发对应的 function call执行本地自定义处理联网数据方法把处理好的数据重新发给模型用户得到最终答案

这里面的过程可以很简单,也可以很复杂

想要准确让模型识别哪些问题需要联网,可以用很复杂的提示词来严格规范

而如何拿到联网搜索的数据,更是很有讲究,可以结合搜索引擎 API + 页面爬虫 + 检查是否满足答案,判断是否进入新的一轮重新搜索,这些完全可以根据具体场景拓展开来

因为本文只是一篇很简单的介绍向博客,因此这里尽可能选择简单的方式进行讲解,具体复杂的做法就看大伙想做到什么程度啦~

下面主要用现成的搜索引擎 API 来搭建这个功能,如果不想借助第三方可以手动用 puppeteer 去手动搜索再爬结果也不是不行

目前市面上可供选择的搜索引擎 API 有:

等等,不过这些官方的要么就是注册麻烦要钱,要么就是只提供给公司不供个人,所以目前我选用的是 serper.dev,它拿的还是谷歌的搜索结果,且注册就送一个月的 2500 条搜索额度,适合快速拿去测功能,如果还有好用的搜索引擎 API 接口也麻烦分享一下哦

下面贴一下我简单测试用的 tool 定义,实际根据个人场景完全自定义即可,此处仅做参考

export const webSearchTool = {  type: 'function',  function: {      name: "search_web",      description: "当需要实时或最新信息时搜索网络。适用于:1)模型知识截止日期后的事件/数据 2)需要最新信息的查询 3)模型不确定的事实性问题",      parameters: {        type: "object",        properties: {          query: {            type: "string",            description: "搜索查询词,应简洁包含关键信息,语言为用户发言语言"          },          current_date: {            type: "string",            description: "当前日期时间,格式为ISO 8601 (YYYY-MM-DD)"          },          time_sensitivity: {            type: "string",            enum: ["high", "medium", "low"],            description: "查询的时效性敏感度:high(需要最新数据),medium(近期数据可接受),low(历史数据可接受)"          },          reason_for_search: {            type: "string",          description: "解释为什么需要搜索而不是使用模型知识"          }        },        required: ["query", "current_date", "reason_for_search"]      }    } }

流程大致代码

const checkChat = await openai.chat.completions.create({    model: models.deepseek,    messages: [{ role: 'user', content: $content }],    tools: [webSearchTool],    tool_choice: 'auto'  })  const checkChatData = checkChat.choices[0].messageconst useFunctionCall = Array.isArray(checkChatData.tool_calls)const messages = [...$historyContent]  // 这里包含之前的想要加入的所有上下文信息if(useFunctionCall) {  const callId = checkChatData.tool_calls[0].id  const functionName = checkChatData.tool_calls[0].function.name  const functionArguments = JSON.parse(checkChatData.tool_calls[0].function.arguments)  // 联网搜索  if(functionName === 'search_web') {    const searchWord = functionArguments.query    const searchContent = await webSearchFunction(searchWord)        // 把联网结果添加进上下文    messages.push(checkChatData)    messages.push({      role: 'tool',      tool_call_id: callId,      content: JSON.stringify(searchContent)    })  }}// 开启新的对话const compeletion = await openai.chat.completions.create({  model: models.deepseek,  messages: messages,})

结语

莫得了,各位闲的没事干可以研究玩玩~

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

AI Function Call 联网搜索 Deepseek QQ机器人
相关文章