开源模型应用落地-工具使用篇-Spring AI-Function Call(八) ...

打印 上一主题 下一主题

主题 935|帖子 935|积分 2809

​​​​​​​一、前言

    通过“开源模型应用落地-工具使用篇-Spring AI(七)-CSDN博客”文章的学习,已经掌握了如何通过Spring AI集成OpenAI和Ollama系列的模型,现在将通过进一步的学习,让Spring AI集成大语言模型更高阶的用法,使得我们能完成更复杂的需求。

二、术语

2.1、Spring AI

  是 Spring 生态系统的一个新项目,它简化了 Java 中 AI 应用程序的创建。它提供以下功能:


  • 支持所有主要模型提供商,例如 OpenAI、Microsoft、Amazon、Google 和 Huggingface。
  • 支持的模型范例包括“聊天”和“文本到图像”,还有更多模型范例正在开发中。
  • 跨 AI 提供商的可移植 API,用于聊天和嵌入模型。
  • 支持同步和流 API 选项。
  • 支持下拉访问模型特定功能。
  • AI 模型输出到 POJO 的映射。
2.2、Function Call

     是 GPT API 中的一项新功能。它可以让开发者在调用 GPT系列模型时,描述函数并让模型智能地输出一个包含调用这些函数所需参数的 JSON 对象。这种功能可以更可靠地将 GPT 的本领与外部工具和 API 举行毗连。
    简单来说就是开放了自定义插件的接口,通过接入外部工具,增强模型的本领。
Spring AI集成Function Call:
Function Calling :: Spring AI Reference


三、前置条件

3.1、JDK 17+

    下载地址:Java Downloads | Oracle
    

  
3.2、创建Maven项目

    SpringBoot版本为3.2.3
  1. <parent>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-parent</artifactId>
  4.     <version>3.2.3</version>
  5.     <relativePath/> <!-- lookup parent from repository -->
  6. </parent>
复制代码
3.3、导入Maven依靠包

  1. <dependency>
  2.         <groupId>org.projectlombok</groupId>
  3.         <artifactId>lombok</artifactId>
  4.         <optional>true</optional>
  5. </dependency>
  6. <dependency>
  7.         <groupId>ch.qos.logback</groupId>
  8.         <artifactId>logback-core</artifactId>
  9. </dependency>
  10. <dependency>
  11.         <groupId>ch.qos.logback</groupId>
  12.         <artifactId>logback-classic</artifactId>
  13. </dependency>
  14. <dependency>
  15.         <groupId>cn.hutool</groupId>
  16.         <artifactId>hutool-core</artifactId>
  17.         <version>5.8.24</version>
  18. </dependency>
  19. <dependency>
  20.         <groupId>org.springframework.ai</groupId>
  21.         <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
  22.         <version>0.8.0</version>
  23. </dependency>
复制代码

3.4、 科学上网的软件


四、技术实现

4.1、新增设置

  1. spring:
  2.   ai:
  3.     openai:
  4.       api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  5.       chat:
  6.         options:
  7.           model: gpt-3.5-turbo
  8.           temperature: 0.45
  9.           max_tokens: 4096
  10.           top-p: 0.9
复制代码
  PS:

  •   openai要替换本身的api-key
  •   模型参数根据实际情况调整
 4.2、新增当地方法类(用于当地回调的function)

  1. import com.fasterxml.jackson.annotation.JsonClassDescription;
  2. import com.fasterxml.jackson.annotation.JsonInclude;
  3. import com.fasterxml.jackson.annotation.JsonProperty;
  4. import com.fasterxml.jackson.annotation.JsonPropertyDescription;
  5. import lombok.extern.slf4j.Slf4j;
  6. import java.util.function.Function;
  7. @Slf4j
  8. public class WeatherService implements Function<WeatherService.Request, WeatherService.Response> {
  9.     /**
  10.      * Weather Function request.
  11.      */
  12.     @JsonInclude(JsonInclude.Include.NON_NULL)
  13.     @JsonClassDescription("Weather API request")
  14.     public record Request(@JsonProperty(required = true,
  15.             value = "location") @JsonPropertyDescription("The city and state e.g.广州") String location) {
  16.     }
  17.     /**
  18.      * Weather Function response.
  19.      */
  20.     public record Response(String weather) {
  21.     }
  22.     @Override
  23.     public WeatherService.Response apply(WeatherService.Request request) {
  24.         log.info("location: {}", request.location);
  25.         String weather = "";
  26.         if (request.location().contains("广州")) {
  27.             weather = "小雨转阴 13~19°C";
  28.         } else if (request.location().contains("深圳")) {
  29.             weather = "阴 15~26°C";
  30.         } else {
  31.             weather = "热到中暑 99~100°C";
  32.         }
  33.         return new WeatherService.Response(weather);
  34.     }
  35. }
复制代码
 4.3、新增设置类

  1. import org.springframework.ai.model.function.FunctionCallback;
  2. import org.springframework.ai.model.function.FunctionCallbackWrapper;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.context.annotation.Description;
  6. import java.util.function.Function;
  7. @Configuration
  8. public class FunctionConfig {
  9.     @Bean
  10.     public FunctionCallback weatherFunctionInfo() {
  11.         return new FunctionCallbackWrapper<WeatherService.Request, WeatherService.Response>("currentWeather", // (1) function name
  12.                 "Get the weather in location", // (2) function description
  13.                 new WeatherService()); // function code
  14.     }
  15. }
复制代码

 4.4、新增Controller类

  1. import cn.hutool.core.collection.CollUtil;
  2. import cn.hutool.core.map.MapUtil;
  3. import jakarta.servlet.http.HttpServletResponse;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.apache.commons.lang3.StringUtils;
  6. import org.springframework.ai.chat.Generation;
  7. import org.springframework.ai.chat.messages.AssistantMessage;
  8. import org.springframework.ai.chat.messages.Message;
  9. import org.springframework.ai.chat.messages.UserMessage;
  10. import org.springframework.ai.chat.prompt.Prompt;
  11. import org.springframework.ai.chat.prompt.SystemPromptTemplate;
  12. import org.springframework.ai.openai.OpenAiChatClient;
  13. import org.springframework.ai.openai.OpenAiChatOptions;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.web.bind.annotation.RequestMapping;
  16. import org.springframework.web.bind.annotation.RestController;
  17. import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
  18. import java.util.List;
  19. @Slf4j
  20. @RestController
  21. @RequestMapping("/api")
  22. public class OpenaiTestController {
  23.     @Autowired
  24.     private OpenAiChatClient openAiChatClient;
  25.     @RequestMapping("/function_call")
  26.     public String function_call(){
  27.         String systemPrompt = "{prompt}";
  28.         SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
  29.         String userPrompt = "广州的天气如何?";
  30.         Message userMessage = new UserMessage(userPrompt);
  31.         Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "你是一个有用的人工智能助手"));
  32.         Prompt prompt = new Prompt(List.of(userMessage, systemMessage), OpenAiChatOptions.builder().withFunction("currentWeather").build());
  33.         List<Generation> response = openAiChatClient.call(prompt).getResults();
  34.         String result = "";
  35.         for (Generation generation : response){
  36.             String content = generation.getOutput().getContent();
  37.             result += content;
  38.         }
  39.         return result;
  40.     }
  41. }
复制代码

五、测试

调用结果:
  浏览器输出:

  idea输出:



六、附带说明

6.1、流式模式不支持Function Call



6.2、更多的模型参数设置

OpenAI Chat :: Spring AI Reference



6.3、qwen系列模型如何支持function call

 通过vllm启动兼容openai接口的api_server,命令如下:
  1. python -m vllm.entrypoints.openai.api_server --served-model-name Qwen1.5-7B-Chat --model Qwen/Qwen1.5-7B-Chat
复制代码
   详细教程参见:
  使用以下代码举行测试:
  1. # Reference: https://openai.com/blog/function-calling-and-other-api-updates
  2. import json
  3. from pprint import pprint
  4. import openai
  5. # To start an OpenAI-like Qwen server, use the following commands:
  6. #   git clone https://github.com/QwenLM/Qwen-7B;
  7. #   cd Qwen-7B;
  8. #   pip install fastapi uvicorn openai pydantic sse_starlette;
  9. #   python openai_api.py;
  10. #
  11. # Then configure the api_base and api_key in your client:
  12. openai.api_base = 'http://localhost:8000/v1'
  13. openai.api_key = 'none'
  14. def call_qwen(messages, functions=None):
  15.     print('input:')
  16.     pprint(messages, indent=2)
  17.     if functions:
  18.         response = openai.ChatCompletion.create(model='Qwen',
  19.                                                 messages=messages,
  20.                                                 functions=functions)
  21.     else:
  22.         response = openai.ChatCompletion.create(model='Qwen',
  23.                                                 messages=messages)
  24.     response = response.choices[0]['message']
  25.     response = json.loads(json.dumps(response,
  26.                                      ensure_ascii=False))  # fix zh rendering
  27.     print('output:')
  28.     pprint(response, indent=2)
  29.     print()
  30.     return response
  31. def test_1():
  32.     messages = [{'role': 'user', 'content': '你好'}]
  33.     call_qwen(messages)
  34.     messages.append({'role': 'assistant', 'content': '你好!很高兴为你提供帮助。'})
  35.     messages.append({
  36.         'role': 'user',
  37.         'content': '给我讲一个年轻人奋斗创业最终取得成功的故事。故事只能有一句话。'
  38.     })
  39.     call_qwen(messages)
  40.     messages.append({
  41.         'role':
  42.         'assistant',
  43.         'content':
  44.         '故事的主人公叫李明,他来自一个普通的家庭,父母都是普通的工人。李明想要成为一名成功的企业家。……',
  45.     })
  46.     messages.append({'role': 'user', 'content': '给这个故事起一个标题'})
  47.     call_qwen(messages)
  48. def test_2():
  49.     functions = [
  50.         {
  51.             'name_for_human':
  52.             '谷歌搜索',
  53.             'name_for_model':
  54.             'google_search',
  55.             'description_for_model':
  56.             '谷歌搜索是一个通用搜索引擎,可用于访问互联网、查询百科知识、了解时事新闻等。' +
  57.             ' Format the arguments as a JSON object.',
  58.             'parameters': [{
  59.                 'name': 'search_query',
  60.                 'description': '搜索关键词或短语',
  61.                 'required': True,
  62.                 'schema': {
  63.                     'type': 'string'
  64.                 },
  65.             }],
  66.         },
  67.         {
  68.             'name_for_human':
  69.             '文生图',
  70.             'name_for_model':
  71.             'image_gen',
  72.             'description_for_model':
  73.             '文生图是一个AI绘画(图像生成)服务,输入文本描述,返回根据文本作画得到的图片的URL。' +
  74.             ' Format the arguments as a JSON object.',
  75.             'parameters': [{
  76.                 'name': 'prompt',
  77.                 'description': '英文关键词,描述了希望图像具有什么内容',
  78.                 'required': True,
  79.                 'schema': {
  80.                     'type': 'string'
  81.                 },
  82.             }],
  83.         },
  84.     ]
  85.     messages = [{'role': 'user', 'content': '(请不要调用工具)\n\n你好'}]
  86.     call_qwen(messages, functions)
  87.     messages.append({
  88.         'role': 'assistant',
  89.         'content': '你好!很高兴见到你。有什么我可以帮忙的吗?'
  90.     }, )
  91.     messages.append({'role': 'user', 'content': '搜索一下谁是周杰伦'})
  92.     call_qwen(messages, functions)
  93.     messages.append({
  94.         'role': 'assistant',
  95.         'content': '我应该使用Google搜索查找相关信息。',
  96.         'function_call': {
  97.             'name': 'google_search',
  98.             'arguments': '{"search_query": "周杰伦"}',
  99.         },
  100.     })
  101.     messages.append({
  102.         'role': 'function',
  103.         'name': 'google_search',
  104.         'content': 'Jay Chou is a Taiwanese singer.',
  105.     })
  106.     call_qwen(messages, functions)
  107.     messages.append(
  108.         {
  109.             'role': 'assistant',
  110.             'content': '周杰伦(Jay Chou)是一位来自台湾的歌手。',
  111.         }, )
  112.     messages.append({'role': 'user', 'content': '搜索一下他老婆是谁'})
  113.     call_qwen(messages, functions)
  114.     messages.append({
  115.         'role': 'assistant',
  116.         'content': '我应该使用Google搜索查找相关信息。',
  117.         'function_call': {
  118.             'name': 'google_search',
  119.             'arguments': '{"search_query": "周杰伦 老婆"}',
  120.         },
  121.     })
  122.     messages.append({
  123.         'role': 'function',
  124.         'name': 'google_search',
  125.         'content': 'Hannah Quinlivan'
  126.     })
  127.     call_qwen(messages, functions)
  128.     messages.append(
  129.         {
  130.             'role': 'assistant',
  131.             'content': '周杰伦的老婆是Hannah Quinlivan。',
  132.         }, )
  133.     messages.append({'role': 'user', 'content': '用文生图工具画个可爱的小猫吧,最好是黑猫'})
  134.     call_qwen(messages, functions)
  135.     messages.append({
  136.         'role': 'assistant',
  137.         'content': '我应该使用文生图API来生成一张可爱的小猫图片。',
  138.         'function_call': {
  139.             'name': 'image_gen',
  140.             'arguments': '{"prompt": "cute black cat"}',
  141.         },
  142.     })
  143.     messages.append({
  144.         'role':
  145.         'function',
  146.         'name':
  147.         'image_gen',
  148.         'content':
  149.         '{"image_url": "https://image.pollinations.ai/prompt/cute%20black%20cat"}',
  150.     })
  151.     call_qwen(messages, functions)
  152. def test_3():
  153.     functions = [{
  154.         'name': 'get_current_weather',
  155.         'description': 'Get the current weather in a given location.',
  156.         'parameters': {
  157.             'type': 'object',
  158.             'properties': {
  159.                 'location': {
  160.                     'type': 'string',
  161.                     'description':
  162.                     'The city and state, e.g. San Francisco, CA',
  163.                 },
  164.                 'unit': {
  165.                     'type': 'string',
  166.                     'enum': ['celsius', 'fahrenheit']
  167.                 },
  168.             },
  169.             'required': ['location'],
  170.         },
  171.     }]
  172.     messages = [{
  173.         'role': 'user',
  174.         # Note: The current version of Qwen-7B-Chat (as of 2023.08) performs okay with Chinese tool-use prompts,
  175.         # but performs terribly when it comes to English tool-use prompts, due to a mistake in data collecting.
  176.         'content': '波士顿天气如何?',
  177.     }]
  178.     call_qwen(messages, functions)
  179.     messages.append(
  180.         {
  181.             'role': 'assistant',
  182.             'content': None,
  183.             'function_call': {
  184.                 'name': 'get_current_weather',
  185.                 'arguments': '{"location": "Boston, MA"}',
  186.             },
  187.         }, )
  188.     messages.append({
  189.         'role':
  190.         'function',
  191.         'name':
  192.         'get_current_weather',
  193.         'content':
  194.         '{"temperature": "22", "unit": "celsius", "description": "Sunny"}',
  195.     })
  196.     call_qwen(messages, functions)
  197. def test_4():
  198.     from langchain.agents import AgentType, initialize_agent, load_tools
  199.     from langchain.chat_models import ChatOpenAI
  200.     llm = ChatOpenAI(
  201.         model_name='Qwen',
  202.         openai_api_base='http://localhost:8000/v1',
  203.         openai_api_key='EMPTY',
  204.         streaming=False,
  205.     )
  206.     tools = load_tools(['arxiv'], )
  207.     agent_chain = initialize_agent(
  208.         tools,
  209.         llm,
  210.         agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
  211.         verbose=True,
  212.     )
  213.     # TODO: The performance is okay with Chinese prompts, but not so good when it comes to English.
  214.     agent_chain.run('查一下论文 1605.08386 的信息')
  215. if __name__ == '__main__':
  216.     print('### Test Case 1 - No Function Calling (普通问答、无函数调用) ###')
  217.     test_1()
  218.     print('### Test Case 2 - Use Qwen-Style Functions (函数调用,千问格式) ###')
  219.     test_2()
  220.     print('### Test Case 3 - Use GPT-Style Functions (函数调用,GPT格式) ###')
  221.     test_3()
  222.     print('### Test Case 4 - Use LangChain (接入Langchain) ###')
  223.     test_4()
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表