背景
大模型通常指的是具有庞大数据的神经网络模型,如OpenAI的GPT系列、Google的BERT等。这些模型对计算资源的需求极高,因此通常部署在云服务器或高性能计算集群上。安装部署大模型时,需要考虑模型的兼容性、计算资源的需求、存储空间的分配以及模型的优化策略。
SpringCloud是一个用于构建微服务架构的框架,它提供了一系列的开发工具和服务,如服务发现、配置管理、负载均衡等。
将大模型接入SpringCloud应用体系,可以实现模型的分布式部署、弹性扩展和高效管理。这一过程需要考虑模型的接口设计、数据传输的效率、安全性以及容错机制等。
交互式访问UI
仓库地址:https://github.com/ollama-ui/ollama-ui
这个项目名为Ollama UI,是一个简单的HTML UI,专为Ollama设计。
使用说明
-
克隆仓库:
git clone https://github.com/ollama-ui/ollama-ui
-
进入克隆下来的目录:
cd ollama-ui
-
启动项目:
make open
-
在浏览器中打开:http://localhost:8000
应用集成大模型
最后我们需要针对业务,对大模型进行应用层面的定制化,比如接入到微服务体系,并赋能业务微服务。下面我挑选了几个常用的,Llama3.1的openApi来简要说明。
api介绍(其中4个)
POST /api/generate
POST /api/chat
POST /api/show
POST /api/embeddings
解析:
-
/api/generate:生成给定提示的响应,该响应将使用所提供的模型。
-
/api/chat:使用所提供的模型生成聊天中的下一条消息。
-
/api/show:显示有关模型的信息,包括详细信息、Modelfile、模板、参数、许可证和系统提示。
-
/api/embeddings:从模型生成嵌入
注意:详细的参数说明,可以参考接口文档 :https://ollama.fan/reference/api/#push-a-model-response
应用集成
创建maven工程
引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>olllama-integration</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.example</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>
<properties>
<!-- You can use later version here if available. -->
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 注册中心客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 客户端负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/net.minidev/json-smart -->
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-ollama -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.ai</groupId>-->
<!-- <artifactId>spring-ai-ollama</artifactId>-->
<!-- <version>1.0.0-M1</version>-->
<!-- </dependency>-->
</dependencies>
<!-- This defines version of all Spring AI related dependencies -->
<dependencyManagement>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/net.minidev/json-smart -->
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<version>2.5.1</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.ai</groupId>-->
<!-- <artifactId>spring-ai-bom</artifactId>-->
<!-- <version>1.0.0-M1</version>-->
<!-- <type>pom</type>-->
<!-- <scope>import</scope>-->
<!-- </dependency>-->
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- As Spring AI 1.0.0 is not yet released and available in Maven central, we'll need a different repository. -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project>
项目配置文件
application.properties
spring.application.name=ollama-integration
## 配置ollama模型参数
ollama.enable=true
ollama.base-url: localhost:11434
ollama.model-name: ollama
bootstrap.properties
server.port=11444
## 注册中心配置
################################### 注册中心 ######################################################
## eurekaClient:服务注册中心的地址
#eureka.client.serviceUrl.defaultZone=http://localhost:8881/eureka/
## eurekaClient:服务注册中心集群的地址
eureka.client.serviceUrl.defaultZone=http://localhost:8881/eureka/,http://localhost2:8882/eureka/
# 拉取缓存清单开关
eureka.client.fetch-registry=true
# 修改缓存清单的更新时间
eureka.client.registry-fetch-interval-seconds=20
启动类
package com.bryant;
import java.time.Duration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* 通过SpringAI框架,集成ollama大模型,并请求
* 参考:
* - https://blog.csdn.net/weixin_54925172/article/details/138815936
* -
*/
@SpringBootApplication
@EnableDiscoveryClient
public class OllamaIntegrationServer {
public static void main(String[] args) {
SpringApplication.run(OllamaIntegrationServer.class, args);
}
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate;
}
/**
* 带超时时间的RestTemplate
* @param restTemplateBuilder
* @return
*/
@Bean("timeOutRestTemplate")
public RestTemplate restTemplate2(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(Duration.ofSeconds(60))
.setReadTimeout(Duration.ofSeconds(60))
.build();
}
}
配置类
package com.bryant.config;
import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(prefix = "ollama", name = "enable", havingValue = "true", matchIfMissing = false)
@Data
public class OllamaConfig {
private String modelName;
private String baseUrl;
}
public class OllamaConstants {
public static final String AI_ASK_PROMPT_TEMPLATE = "I am a student from the University of Washington."
+ "I want to ask you a question: %s ";
public static String CITY_GUIDE_PROMPT_TEMPLATE = "I am a tourist visiting the city of %s."
+ "I am mostly interested in %s."
+ "Tell me tips on what to do there.";
public static final String OLLAMA_MODEL_NAME = "llama3";
public static final String OLLAMA_URL = "http://127.0.0.1:11434";
//生成补全 POST
public static final String API_GENERATE = OLLAMA_URL + "/api/generate";
//对话补全 POST
public static final String API_CHAT = OLLAMA_URL + "/api/chat";
//列出本地模型
public static final String API_LIST = OLLAMA_URL + "/api/tags";
//显示模型信息
public static final String API_SHOW = OLLAMA_URL + "/api/show";
//生成嵌入
public static final String API_EMBEDDINGS = OLLAMA_URL + "/api/embeddings";
}
接口服务
Controller
package com.bryant.controller;
import com.bryant.service.ChatService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AiController {
private final ChatService chatService;
public AiController(ChatService chatService) {
this.chatService = chatService;
}
@GetMapping("/city-guide")
public String askAi(
@RequestParam("city") String city,
@RequestParam("interest") String interest) {
return chatService.getCityGuide(city, interest);
}
@GetMapping("/ask_ai")
public String cityGuide(@RequestParam("question") String question) {
return chatService.askAi(question);
}
@PostMapping("/embedding")
public String embedding(
@RequestParam("prompt") String prompt,
@RequestParam("temperature") Integer temperature) {
return chatService.embedding(prompt, temperature);
}
@GetMapping("/model_show")
public String modelShow() {
return chatService.modelShow();
}
}
Service
package com.bryant.service;
import com.bryant.config.OllamaConstants;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class ChatService {
@Autowired
private RestTemplate restTemplate;
@Autowired
@Qualifier("timeOutRestTemplate")
private RestTemplate timeOutRestTemplate;
public String getCityGuide(String city, String interest) {
String prompt = String.format(OllamaConstants.CITY_GUIDE_PROMPT_TEMPLATE, city, interest);
HashMap<String, Object> map = new HashMap<>();
map.put("prompt", prompt);
map.put("model", OllamaConstants.OLLAMA_MODEL_NAME);
map.put("stream", false);
ResponseEntity<JsonNode> stringResponseEntity = timeOutRestTemplate.postForEntity(OllamaConstants.API_GENERATE, map, JsonNode.class);
HttpStatus statusCode = stringResponseEntity.getStatusCode();
// 大模型请求成功
if (statusCode != HttpStatus.OK) {
return "first request to ollama failed";
}
JsonNode body = stringResponseEntity.getBody();
JsonNode firstAnswerResponseText = body.get("response");
// 第二轮对话
map.put("prompt", "so can you tell me more about it?");
map.put("model", OllamaConstants.OLLAMA_MODEL_NAME);
map.put("context", body.get("context"));
map.put("stream", false);
ResponseEntity<JsonNode> stringResponseEntity2 = timeOutRestTemplate.postForEntity(OllamaConstants.API_GENERATE, map, JsonNode.class);
JsonNode body2 = stringResponseEntity2.getBody();
JsonNode firstAnswerResponseText2 = body2.get("response");
return "firstAnswerResponseText: " + firstAnswerResponseText.asText()
+ "\n"
+ " secondAnswerResponseText: " + firstAnswerResponseText2.asText();
}
public String askAi(String question) {
String prompt = String.format(OllamaConstants.AI_ASK_PROMPT_TEMPLATE, question);
HashMap<String, Object> map = new HashMap<>();
map.put("prompt", prompt);
map.put("model", OllamaConstants.OLLAMA_MODEL_NAME);
map.put("stream", false);
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(OllamaConstants.API_GENERATE, map, String.class);
return stringResponseEntity.getBody();
}
public String embedding(String prompt, Integer temperature) {
HashMap<String, Object> map = new HashMap<>();
map.put("prompt", prompt);
map.put("model", OllamaConstants.OLLAMA_MODEL_NAME);
HashMap<String, Object> options = new HashMap<>();
map.put("options", options);
// temperature 模型的温度。增加温度将使模型回答更具创造性。(默认:0.8)
options.put("temperature", temperature);
map.put("stream", false);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(OllamaConstants.API_EMBEDDINGS, map, String.class);
return responseEntity.getBody();
}
public String modelShow() {
HashMap<String, Object> map = new HashMap<>();
map.put("model", OllamaConstants.OLLAMA_MODEL_NAME);
map.put("stream", false);
ResponseEntity<String> responseEntity = restTemplate.postForEntity(OllamaConstants.API_SHOW, map, String.class);
return responseEntity.getBody();
}
}
注册中心启动
大模型应用已经集成到SpringCloud微服务
接口测试结果
api访问大模型,并取到大模型结果。
http://localhost:11444/city-guide?city=beijing&interest=fishing
http://localhost:11444/city-guide?city=beijing&interest=fishing
http://localhost:11444/model_show
embedding
http://localhost:11444/embedding?prompt=sky is red&temperature=0
结果分析
每个请求大模型,都会返回详细的执行步骤耗时,如下:
"total_duration":16875943917,
"load_duration":3372417,
"prompt_eval_count":31,
"prompt_eval_duration":292574000,
"eval_count":502,
"eval_duration":16578831000
-
total_duration
:生成响应的时间 -
load_duration
:加载模型的时间(纳秒) -
prompt_eval_count
:提示中的令牌token数量 -
prompt_eval_duration
:评估提示的时间(纳秒) -
eval_count
:响应中的令牌token数量 -
eval_duration
:生成响应所花费的时间(纳秒) -
context
:此响应中使用的会话的编码,这可以在下一个请求中发送以保持会话记忆 -
response
:如果响应是流式的,则为空;如果不是流式的,则会包含完整响应
拓展
生成embedding
大型语言模型可以生成上下文相关的embedding表示,这些embedding可以更好地捕捉单词的语义和上下文信息。
embedding向量是包含语义信息的。也就是含义相近的单词,embedding向量在空间中有相似的位置,但是,除此之外,embedding也有其它优点。
Embedding重要性
而embedding重要的原因在于它可以表示单词或者语句的语义。实值向量的embedding可以表示单词的语义,主要是因为这些embedding向量是根据单词在语言上下文中的出现模式进行学习的。
例如,如果一个单词在一些上下文中经常与另一个单词一起出现,那么这两个单词的嵌入向量在向量空间中就会有相似的位置。这意味着它们有相似的含义和语义。
Embedding在大模型中的价值
前面说的其实都是Embedding在之前的价值。但是,大语言模型时代,例如ChatGPT这样的模型流行之后,大家发现embedding有了新的价值,即解决大模型的输入限制。
此前,OpenAI官方也发布了一个案例,即如何使用embedding来解决长文本输入问题,我们DataLearner官方博客也介绍了这个教程:OpenAI官方教程:如何使用基于embeddings检索来解决GPT无法处理长文本和最新数据的问题。
像 GPT-3 这样的语言模型有一个限制,即它们可以处理的输入文本量有限。这个限制通常在几千到数万个tokens之间,具体取决于模型架构和可用的硬件资源。