当前位置:   article > 正文

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

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

一、前言

    通过“开源模型应用落地-工具使用篇-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+

    下载地址:https://www.oracle.com/java/technologies/downloads/#jdk17-windows

    

  

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:

  1.   openai要替换自己的api-key
  2.   模型参数根据实际情况调整

 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,命令如下:

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

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

闽ICP备14008679号