???????一、前言
? ? 通過“開源模型應用落地-工具使用篇-Spring AI(七)-CSDN博客”文章的學習,已經(jīng)掌握了如何通過Spring AI集成OpenAI和Ollama系列的模型,現(xiàn)在將通過進一步的學習,讓Spring AI集成大語言模型更高階的用法,使得我們能完成更復雜的需求。
二、術語
2.1、Spring AI
? 是 Spring 生態(tài)系統(tǒng)的一個新項目,它簡化了 Java 中 AI 應用程序的創(chuàng)建。它提供以下功能:
- 支持所有主要模型提供商,例如 OpenAI、Microsoft、Amazon、Google 和 Huggingface。
- 支持的模型類型包括“聊天”和“文本到圖像”,還有更多模型類型正在開發(fā)中。
- 跨 AI 提供商的可移植 API,用于聊天和嵌入模型。
- 支持同步和流 API 選項。
- 支持下拉訪問模型特定功能。
- AI 模型輸出到 POJO 的映射。
2.2、Function Call
? ? ?是 GPT API 中的一項新功能。它可以讓開發(fā)者在調(diào)用 GPT系列模型時,描述函數(shù)并讓模型智能地輸出一個包含調(diào)用這些函數(shù)所需參數(shù)的 JSON 對象。這種功能可以更可靠地將 GPT 的能力與外部工具和 API 進行連接。
? ? 簡單來說就是開放了自定義插件的接口,通過接入外部工具,增強模型的能力。
Spring AI集成Function Call:
Function Calling :: Spring AI Reference
三、前置條件
3.1、JDK 17+
? ? 下載地址:Java Downloads | Oracle
? ??
??
3.2、創(chuàng)建Maven項目
? ? SpringBoot版本為3.2.3
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
3.3、導入Maven依賴包
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.24</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>0.8.0</version>
</dependency>
3.4、 科學上網(wǎng)的軟件
四、技術實現(xiàn)
4.1、新增配置
spring:
ai:
openai:
api-key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
chat:
options:
model: gpt-3.5-turbo
temperature: 0.45
max_tokens: 4096
top-p: 0.9
? PS:
- ? openai要替換自己的api-key
- ? 模型參數(shù)根據(jù)實際情況調(diào)整
?4.2、新增本地方法類(用于本地回調(diào)的function)
import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import lombok.extern.slf4j.Slf4j;
import java.util.function.Function;
@Slf4j
public class WeatherService implements Function<WeatherService.Request, WeatherService.Response> {
/**
* Weather Function request.
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonClassDescription("Weather API request")
public record Request(@JsonProperty(required = true,
value = "location") @JsonPropertyDescription("The city and state e.g.廣州") String location) {
}
/**
* Weather Function response.
*/
public record Response(String weather) {
}
@Override
public WeatherService.Response apply(WeatherService.Request request) {
log.info("location: {}", request.location);
String weather = "";
if (request.location().contains("廣州")) {
weather = "小雨轉陰 13~19°C";
} else if (request.location().contains("深圳")) {
weather = "陰 15~26°C";
} else {
weather = "熱到中暑 99~100°C";
}
return new WeatherService.Response(weather);
}
}
?4.3、新增配置類
import org.springframework.ai.model.function.FunctionCallback;
import org.springframework.ai.model.function.FunctionCallbackWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Description;
import java.util.function.Function;
@Configuration
public class FunctionConfig {
@Bean
public FunctionCallback weatherFunctionInfo() {
return new FunctionCallbackWrapper<WeatherService.Request, WeatherService.Response>("currentWeather", // (1) function name
"Get the weather in location", // (2) function description
new WeatherService()); // function code
}
}
?4.4、新增Controller類
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.ai.chat.Generation;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.openai.OpenAiChatClient;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/api")
public class OpenaiTestController {
@Autowired
private OpenAiChatClient openAiChatClient;
@RequestMapping("/function_call")
public String function_call(){
String systemPrompt = "{prompt}";
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
String userPrompt = "廣州的天氣如何?";
Message userMessage = new UserMessage(userPrompt);
Message systemMessage = systemPromptTemplate.createMessage(MapUtil.of("prompt", "你是一個有用的人工智能助手"));
Prompt prompt = new Prompt(List.of(userMessage, systemMessage), OpenAiChatOptions.builder().withFunction("currentWeather").build());
List<Generation> response = openAiChatClient.call(prompt).getResults();
String result = "";
for (Generation generation : response){
String content = generation.getOutput().getContent();
result += content;
}
return result;
}
}
五、測試
調(diào)用結果:
? 瀏覽器輸出:
? idea輸出:
六、附帶說明
6.1、流式模式不支持Function Call
6.2、更多的模型參數(shù)配置
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
? ?詳細教程參見:文章來源:http://www.zghlxwxcb.cn/news/detail-839637.html
? 使用以下代碼進行測試:文章來源地址http://www.zghlxwxcb.cn/news/detail-839637.html
# Reference: https://openai.com/blog/function-calling-and-other-api-updates
import json
from pprint import pprint
import openai
# To start an OpenAI-like Qwen server, use the following commands:
# git clone https://github.com/QwenLM/Qwen-7B;
# cd Qwen-7B;
# pip install fastapi uvicorn openai pydantic sse_starlette;
# python openai_api.py;
#
# Then configure the api_base and api_key in your client:
openai.api_base = 'http://localhost:8000/v1'
openai.api_key = 'none'
def call_qwen(messages, functions=None):
print('input:')
pprint(messages, indent=2)
if functions:
response = openai.ChatCompletion.create(model='Qwen',
messages=messages,
functions=functions)
else:
response = openai.ChatCompletion.create(model='Qwen',
messages=messages)
response = response.choices[0]['message']
response = json.loads(json.dumps(response,
ensure_ascii=False)) # fix zh rendering
print('output:')
pprint(response, indent=2)
print()
return response
def test_1():
messages = [{'role': 'user', 'content': '你好'}]
call_qwen(messages)
messages.append({'role': 'assistant', 'content': '你好!很高興為你提供幫助。'})
messages.append({
'role': 'user',
'content': '給我講一個年輕人奮斗創(chuàng)業(yè)最終取得成功的故事。故事只能有一句話。'
})
call_qwen(messages)
messages.append({
'role':
'assistant',
'content':
'故事的主人公叫李明,他來自一個普通的家庭,父母都是普通的工人。李明想要成為一名成功的企業(yè)家。……',
})
messages.append({'role': 'user', 'content': '給這個故事起一個標題'})
call_qwen(messages)
def test_2():
functions = [
{
'name_for_human':
'谷歌搜索',
'name_for_model':
'google_search',
'description_for_model':
'谷歌搜索是一個通用搜索引擎,可用于訪問互聯(lián)網(wǎng)、查詢百科知識、了解時事新聞等。' +
' Format the arguments as a JSON object.',
'parameters': [{
'name': 'search_query',
'description': '搜索關鍵詞或短語',
'required': True,
'schema': {
'type': 'string'
},
}],
},
{
'name_for_human':
'文生圖',
'name_for_model':
'image_gen',
'description_for_model':
'文生圖是一個AI繪畫(圖像生成)服務,輸入文本描述,返回根據(jù)文本作畫得到的圖片的URL。' +
' Format the arguments as a JSON object.',
'parameters': [{
'name': 'prompt',
'description': '英文關鍵詞,描述了希望圖像具有什么內(nèi)容',
'required': True,
'schema': {
'type': 'string'
},
}],
},
]
messages = [{'role': 'user', 'content': '(請不要調(diào)用工具)\n\n你好'}]
call_qwen(messages, functions)
messages.append({
'role': 'assistant',
'content': '你好!很高興見到你。有什么我可以幫忙的嗎?'
}, )
messages.append({'role': 'user', 'content': '搜索一下誰是周杰倫'})
call_qwen(messages, functions)
messages.append({
'role': 'assistant',
'content': '我應該使用Google搜索查找相關信息。',
'function_call': {
'name': 'google_search',
'arguments': '{"search_query": "周杰倫"}',
},
})
messages.append({
'role': 'function',
'name': 'google_search',
'content': 'Jay Chou is a Taiwanese singer.',
})
call_qwen(messages, functions)
messages.append(
{
'role': 'assistant',
'content': '周杰倫(Jay Chou)是一位來自臺灣的歌手。',
}, )
messages.append({'role': 'user', 'content': '搜索一下他老婆是誰'})
call_qwen(messages, functions)
messages.append({
'role': 'assistant',
'content': '我應該使用Google搜索查找相關信息。',
'function_call': {
'name': 'google_search',
'arguments': '{"search_query": "周杰倫 老婆"}',
},
})
messages.append({
'role': 'function',
'name': 'google_search',
'content': 'Hannah Quinlivan'
})
call_qwen(messages, functions)
messages.append(
{
'role': 'assistant',
'content': '周杰倫的老婆是Hannah Quinlivan。',
}, )
messages.append({'role': 'user', 'content': '用文生圖工具畫個可愛的小貓吧,最好是黑貓'})
call_qwen(messages, functions)
messages.append({
'role': 'assistant',
'content': '我應該使用文生圖API來生成一張可愛的小貓圖片。',
'function_call': {
'name': 'image_gen',
'arguments': '{"prompt": "cute black cat"}',
},
})
messages.append({
'role':
'function',
'name':
'image_gen',
'content':
'{"image_url": "https://image.pollinations.ai/prompt/cute%20black%20cat"}',
})
call_qwen(messages, functions)
def test_3():
functions = [{
'name': 'get_current_weather',
'description': 'Get the current weather in a given location.',
'parameters': {
'type': 'object',
'properties': {
'location': {
'type': 'string',
'description':
'The city and state, e.g. San Francisco, CA',
},
'unit': {
'type': 'string',
'enum': ['celsius', 'fahrenheit']
},
},
'required': ['location'],
},
}]
messages = [{
'role': 'user',
# Note: The current version of Qwen-7B-Chat (as of 2023.08) performs okay with Chinese tool-use prompts,
# but performs terribly when it comes to English tool-use prompts, due to a mistake in data collecting.
'content': '波士頓天氣如何?',
}]
call_qwen(messages, functions)
messages.append(
{
'role': 'assistant',
'content': None,
'function_call': {
'name': 'get_current_weather',
'arguments': '{"location": "Boston, MA"}',
},
}, )
messages.append({
'role':
'function',
'name':
'get_current_weather',
'content':
'{"temperature": "22", "unit": "celsius", "description": "Sunny"}',
})
call_qwen(messages, functions)
def test_4():
from langchain.agents import AgentType, initialize_agent, load_tools
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(
model_name='Qwen',
openai_api_base='http://localhost:8000/v1',
openai_api_key='EMPTY',
streaming=False,
)
tools = load_tools(['arxiv'], )
agent_chain = initialize_agent(
tools,
llm,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
)
# TODO: The performance is okay with Chinese prompts, but not so good when it comes to English.
agent_chain.run('查一下論文 1605.08386 的信息')
if __name__ == '__main__':
print('### Test Case 1 - No Function Calling (普通問答、無函數(shù)調(diào)用) ###')
test_1()
print('### Test Case 2 - Use Qwen-Style Functions (函數(shù)調(diào)用,千問格式) ###')
test_2()
print('### Test Case 3 - Use GPT-Style Functions (函數(shù)調(diào)用,GPT格式) ###')
test_3()
print('### Test Case 4 - Use LangChain (接入Langchain) ###')
test_4()
到了這里,關于開源模型應用落地-工具使用篇-Spring AI-Function Call(八)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!