当前位置:   article > 正文

Spring AI Function 的实现原理?

spring ai function

在前面 Spring AI Chat 简单示例 中介绍了 Chat 中的 Function 用法,我很好奇这个 Function 是如何被调用的,就在下面代码中加了断点看执行:

在这里插入图片描述

执行过程中进入了这个方法,并且符合 Request 类型的参数,这是如何实现的?就像魔法一样神奇,这是 Spring AI 的功能还是 OpenAI 的功能?好奇心引导我必须深入看看。

跟踪代码 - 加日志

先找了几个断点尝试跟踪,没有特别好的入口点。在遇到类似问题的时候,你可以试试先把日志级别调成DEBUG或者TRACE,Spring Boot 默认使用的 logback,所以在当前项目 src/main/resources 目录中添加 logback.xml 配置文件:

<configuration scan="true">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger:%L - %msg%n</pattern>
        </encoder>
    </appender>

    <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook">
        <delay>5 seconds</delay>
    </shutdownHook>

    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

配置日志后执行代码,发现了两次 OpenAI 的方法调用,日志中有参数,下面是第一次调用的日志:

org.springframework.web.client.DefaultRestClient:437
Writing [ChatCompletionRequest[messages=[
ChatCompletionMessage[
content=北京、石家庄的天气怎么样,适合穿什么衣服?温度保留1........太长了省略...
  • 1
  • 2
  • 3
  • 4

日志的内容看着就是 map.toString() 的样子,效果不好,不如转成 JSON 展示。

跟踪代码 - 看数据

打开 DefaultRestClient:437 行加断点,然后使用表达式将结果转换为 JSON,下面是操作截图:

在这里插入图片描述

将JSON复制出来,内容如下:

{
  "messages": [
    {
      "content": "北京、石家庄的天气怎么样,适合穿什么衣服?温度保留1位小数。",
      "role": "user"
    }
  ],
  "model": "gpt-3.5-turbo",
  "max_tokens": 200,
  "stream": false,
  "temperature": 0.4,
  "tools": [
    {
      "type": "function",
      "function": {
        "description": "Get the weather in location",
        "name": "CurrentWeather",
        "parameters": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "location": { "type": "string" },
            "unit": { "type": "string", "enum": ["C", "F"] }
          }
        }
      }
    }
  ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

上面执行的内容中,除了我们的 messages 信息外,还多了 tools 信息,这里是我们定义的 CurrentWeather,这里的数据是在注册的时候生成的:

FunctionCallbackWrapper.builder(new MockWeatherService())
                        .withName("CurrentWeather")
                        .withDescription("Get the weather in location").build()
  • 1
  • 2
  • 3

在上面的 build() 方法调用时,通过下面的代码初始化了 inputTypeSchema

if (this.inputType == null) {
	this.inputType = resolveInputType(this.function);
}

if (this.inputTypeSchema == null) {
	boolean upperCaseTypeValues = this.schemaType == SchemaType.OPEN_API_SCHEMA;
	this.inputTypeSchema = ModelOptionsUtils.getJsonSchema(this.inputType, upperCaseTypeValues);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在调用方法的时候我们指定了要使用 CurrentWeather,就把这个函数的信息写入了请求的 JSON 中。

OpenAI Function 文档

在 OpenAI 官方文档中 Function calling 中有介绍,在 Create chat completion有官方的 Function 功能示例和参数定义:

在这里插入图片描述

客户端执行函数

OpenAI会按照格式要求返回数据,本文示例返回的数据在 DefaultRestClient 639 行断点查看返回值,返回JSON如下:

{
  "id": "chatcmpl-953FrCIXAc3l4qmmY4u6cx48hecGN",
  "choices": [
    {
      "finish_reason": "tool_calls",
      "index": 0,
      "message": {
        "role": "assistant",
        "tool_calls": [
          {
            "id": "call_nq3fMQLC6MRz4ZaNcYRey18C",
            "type": "function",
            "function": {
              "name": "CurrentWeather",
              "arguments": "{\"location\": \"北京\", \"unit\": \"C\"}"
            }
          },
          {
            "id": "call_OJCtiJdxqWFWt1MF2taIbfvW",
            "type": "function",
            "function": {
              "name": "CurrentWeather",
              "arguments": "{\"location\": \"石家庄\", \"unit\": \"C\"}"
            }
          }
        ]
      }
    }
  ],
  "created": 1710991179,
  "model": "gpt-3.5-turbo-0125",
  "system_fingerprint": "fp_4f0b692a78",
  "object": "chat.completion",
  "usage": { "completion_tokens": 55, "prompt_tokens": 93, "total_tokens": 148 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

客户端需要在自己本地使用参数调用Function,下面看看这段代码的逻辑。

在这里插入图片描述

第一次调用得到返回值时,上图124行代码的 response 会经过下面 handleFunctionCallOrReturn 方法的判断,如果不包含 toolFunctionCall 就直接返回,如果包含就继续处理,在 145行的 doCreateToolResponseRequest 方法中 Spring AI 将 arguments 反序列化为参数对象然后调用 Function。
然后将 Function 的结果添加到会话历史 messages 中再次调用 147 行的 callWithFunctionSupport 方法形成递归,这里会在 124 行再次调用 OpenAI,下面是合并后的参数:

{
  "messages": [
    {
      "content": "北京、石家庄的天气怎么样,适合穿什么衣服?温度保留1位小数。",
      "role": "user"
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "id": "call_bSRms7bCa3ZDpSlMIs1Hzfc8",
          "type": "function",
          "function": {
            "name": "CurrentWeather",
            "arguments": "{\"location\": \"北京\", \"unit\": \"C\"}"
          }
        },
        {
          "id": "call_jQ9nBPsOzJGsBrwYBVHhd6iw",
          "type": "function",
          "function": {
            "name": "CurrentWeather",
            "arguments": "{\"location\": \"石家庄\", \"unit\": \"C\"}"
          }
        }
      ]
    },
    {
      "content": "{\"temp\":6.054343984099231,\"unit\":\"C\"}",
      "role": "tool",
      "name": "CurrentWeather",
      "tool_call_id": "call_bSRms7bCa3ZDpSlMIs1Hzfc8"
    },
    {
      "content": "{\"temp\":31.57208925230239,\"unit\":\"C\"}",
      "role": "tool",
      "name": "CurrentWeather",
      "tool_call_id": "call_jQ9nBPsOzJGsBrwYBVHhd6iw"
    }
  ],
  "model": "gpt-3.5-turbo",
  "max_tokens": 200,
  "stream": false,
  "temperature": 0.4,
  "tools": [
    {
      "type": "function",
      "function": {
        "description": "Get the weather in location",
        "name": "CurrentWeather",
        "parameters": {
          "$schema": "https://json-schema.org/draft/2020-12/schema",
          "type": "object",
          "properties": {
            "location": { "type": "string" },
            "unit": { "type": "string", "enum": ["C", "F"] }
          }
        }
      }
    }
  ]
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

消息的角色分别是 user、assistant、tool,此次请求返回结果中没有了 toolFunctionCall ,这就是我们需要的最终结果,JSON如下:

{
  "id": "chatcmpl-953TUtPNLBaeqfXGDMSW6fRnjm1VF",
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "content": "北京的天气为23.1°C,石家庄的天气为0.1°C。根据当前天气情况,建议穿着:\n\n- 北京:春秋过渡季节,建议穿着长袖衣物或薄外套。\n- 石家庄:寒冷天气,建议穿着厚外套、围巾和手套等保暖衣物。",
        "role": "assistant"
      }
    }
  ],
  "created": 1710992024,
  "model": "gpt-3.5-turbo-0125",
  "system_fingerprint": "fp_fa89f7a861",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 124,
    "prompt_tokens": 197,
    "total_tokens": 321
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

注意: 上面的截图和JSON分别是不同调用产生的,数据不一致。

经过上面的分析了解了 Function 的实现原理后,在使用提示词和 Function 时需要有一定的联系,需要让 OpenAI 能够推测中我们需要的参数,将 Function 的执行结果作为追加的提示词,让 OpenAI 利用追加的信息来生成最终的结果。

根据 Function 的用法,我来假设一个场景,比如在一个支持根据语音转文字,文字在通过 OpenAI 生成出差申请的场景时,可以口述:“我要在下周一出差从石家庄到北京,坐高铁去客户现场,和客户讨论具体的技术方案。”,我们可以将信息转换为 Function 所需的参数,然后校验信息,符合要求后调用我们业务接口创建出差申请,我们下一篇就结合提示词模板和 Function 来简单实现这个功能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/743786
推荐阅读
相关标签
  

闽ICP备14008679号