導讀
本文以快速開發一個 AI 原生應用為目的,介紹了 Spring AI 的包括對話模型、提示詞模板、Function Calling、結構化輸出、圖片生成、向量化、向量資料庫等全部核心功能,並介紹了檢索增強生成的技術。依賴 Spring AI 提供的功能,我們可以輕鬆開發出一個簡單的 AI 原生應用。
全文34894字,預計閱讀時間45分鐘。
01 摘要
1.1 AI原生應用
什麼是「AI原生應用」?簡單一句話就是,AI帶來應用的最核心價值,如果脫離開 AI,應用將不存在。
AI原生應用是指在設計、開發、部署、運營和維護過程中,具有內在安全、可信的AI能力應用,其中AI是功能的自然組成部分。
實現一個AI原生應用的過程,往往需要利用資料驅動和基於知識的生態系統,在這一過程中,資料與知識將被消費和生產,以實現新的基於AI的原生功能。在需要時透過學習和自適應的AI增強能力,來替代以往靜態的、基於規則的機制。
在理解AI原生應用時需要區分「AI原生」與「基於AI」的區別:
AI原生:如前文提到的,AI 帶來應用的最核心價值,如果脫離開 AI,應用將不存在。
基於AI:指使用AI為使用者提供新功能,從這個方面看,AI 就是一個附加元件。
1.2 無處不在的AI
從實現的角度來看,新增 AI 能力到一個系統可以採用不同的方法:
第一種方法是用 AI 技術替換已有的功能模組,比較方便對比替換前後的收益;
第二種方法是新增一個全新的基於 AI 的模組,這種模組沒有任何歷史包袱,適合在探索性專案中應用;
第三種方法是新增一個基於 AI 的模組,由它驅動傳統模組,在傳統模組之上,提供基於 AI 的自動化、最佳化或額外的功能。
△新增 AI 能力到一個應用系統的不同方法
但無論是簡單地使用 AI 替換一個、多個或所有模組中的現有功能,甚至新增新功能,這些並不能實現嚴格意義上的 AI Native,類比到應用架構中:AI 應該和程式碼、資料一樣成為一等公民,即無程式碼、不程式設計;無 AI、不工作。一等公民可以在整個架構中橫向、縱向使用,而不僅限於某一層,資料基礎設施也是如此,資料和知識需要跨層共享,AI 技術也可以應用於每一層甚至跨層,以實現架構中無處不在的 AI。
1.3 Spring AI 專案
實現一個 AI 原生應用不僅僅需要強大的模型基座能力,還需要基於大模型,做應用的工程開發。Spring AI 專案旨在簡化包含大模型的應用程式的開發,減少不必要的複雜功能開發。
該專案的靈感來自一些著名的Python專案,如 LangChain 和 LlamaIndex,但Spring AI並不是這些專案的直接複製,而是基於Java獨立開發的。該專案的成立是因為作者相信:下一波Generative AI應用程式將不僅面向Python開發人員,而且將在許多程式語言中無處不在。
Spring AI的核心是提供抽象介面,作為開發人工智慧應用程式的基礎。這些抽象介面有多個實現,能夠以最小的程式碼更改實現簡單的大模型元件。
Spring AI提供以下功能,後文將逐一介紹:
支援所有主要的模型提供商,如OpenAI、微軟、亞馬遜、谷歌和Huggingface;
支援的模型型別有聊天和文字到影象,還有音影片等;
可移植的大模型提供商 chat 和 embedding 模型,同時支援同步和流API選項,還支援選擇不同功能模型的功能;
大模型輸出到 POJO 的對映;
支援所有主流的的向量庫,如Azure Vector、Chroma、Milvus、Neo4j、PostgreSQL/PGVector、PineCone、Qdrant、Redis和Weaviate;
函式呼叫 function calling;
用於大模型和向量庫的Spring Boot自動配置和啟動器;
用於資料工程的ETL框架。
02 開發前準備
要求springboot版本 3.2.0+,否則無法執行。
以 Maven 開發專案為例,首先在父pom 中的 dependencyManagement 新增依賴版本控制
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>0.8.1-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
在模組中引入以下依賴 dependency
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- openai --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency>
application.yaml 加入 api-key 等配置即可
spring: ai: openai: api-key: sk-OYLslZxxx chat: options: model: gpt-3.5-turbo temperature: 0.7 embedding: options: model: text-embedding-ada-002
引入這些依賴並填好環境變數配置,就可以用 Spring AI 進行AI原生應用開發了~
03 Spring AI 核心功能
3.1 對話模型
3.1.1 對話模型概念
Chat Completion API幫助開發人員輕鬆將AI聊天完成功能融入應用程式。它利用預訓練的語言模型,如GPT(Generative pre-trained Transformer),以自然語言生成對使用者輸入的類人響應。
API通常透過向人工智慧模型傳送提示或部分對話來工作,人工智慧模型會依據其訓練資料和自然語言模式理解,生成對話。然後將完成的響應返回給應用程式,應用程式可以將其呈現給使用者或用於進一步處理。
△Spring AI 對話模型實現原理
3.1.2 程式碼示例
OpenAiChatClient 實體會被自動註冊到 Spring 容器 中,直接 @Autowired 引用即可。
Chat 模型會根據使用者的輸入,呼叫大模型,返回大模型的結果。
@RestController public class AiController { @Autowired private OpenAiChatClient chatClient; @GetMapping("/ai/generate") public Response generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { return Response.ok(chatClient.call(message)); } @GetMapping("/ai/generateStream") public Flux<ChatResponse> generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { Prompt prompt = new Prompt(new UserMessage(message)); return chatClient.stream(prompt); } }
call 和 stream 方法分表對應大模型的兩種輸出方式:
非流式輸出 call:等待大模型把回答結果全部生成後輸出給使用者;
流式輸出 stream:逐個字元輸出,一方面符合大模型生成方式的本質,另一方面當模型推理效率不是很高時,流式輸出比起全部生成後再輸出大大提高使用者體驗。
呼叫示例:
localhost:8080/ai/generate?message=推薦林俊杰的五首歌 { "errno": 0, "msg": "success", "data": "1. 《江南》\n2. 《愛不單行》\n3. 《背對背擁抱》\n4. 《修煉愛情》\n5. 《她說》" }
3.2 提示詞模板
3.2.1 提示詞概念
提示詞是引導大模型生成特定輸出的輸入,提示詞的設計和措辭會極大地影響模型的響應結果。
[推薦林俊杰的五首歌] 就是一個最簡單的提示詞。在AI領域,提示的結構隨著時間的推移不斷髮展,最初的提示只是簡單的字串,隨著時間的推移,提示詞開始包含特定輸入的佔位符,例如"USER:"、"ASSISTANT"這些,大模型可以識別這些佔位符,並給出更符合使用者需求的內容輸出。
OpenAI 後來引入了一種更具條理的方法。在他們的模型中,提示不再僅僅是簡單的文字字串,而是由一系列帶有特定角色的訊息構成。儘管每條訊息依然是文字形式,但它們各自被賦予了明確的角色。這些角色不僅幫助分類訊息,還進一步澄清了大模型提示詞中每個部分的上下文和目的。這種結構化的方法極大地提升了與大模型溝通的準確性和效率,因為提示詞中的每個元素在都承載著獨特且清晰的功能。
下面是一個帶有角色資訊的prompt示例,可以看到prompt是包括對話的上下文,可以讓模型更好的理解使用者的意圖。
{"role":"user","content":"你好"}, {"role":"assistant","content":"你好,請問有什麼我可以幫助你的嗎?無論你有什麼問題或需要幫助,我都會盡力回答和協助你。請隨時告訴我你的需求。"}, {"role":"user","content": "我在上海,週末可以去哪裏玩?"}, {"role":"assistant","content": "上海是一個充滿活力和文化氛圍的城市,有很多適合週末遊玩的地方。以下是幾個值得推薦的地方:\n\n1. 上海科技館:上海科技館是中國大陸最大的科技館之一,有許多互動展覽和科學實驗室,可以讓您瞭解科技的發展歷史和最新成果。\n2. 上海博物館:上海博物館是一座集藝術、歷史和文化於一體的博物館,有許多精美的藝術品和文物,可以讓您深入瞭解中國和世界的文化歷史。\n3. 外灘:外灘是上海的標誌性景點之一,有許多高樓大廈和繁華的商業街區,是欣賞黃浦江美景和城市風貌的好去處。\n4. 上海迪士尼樂園:上海迪士尼樂園是世界上最大的迪士尼主題公園之一,有許多精彩的遊樂專案和演出,可以讓您盡情享受娛樂和歡樂。\n5. 上海野生動物園:上海野生動物園是一個以自然保護為主題的野生動物園,有許多珍稀動物和植物,可以讓您近距離接觸大自然。\n\n這些地方都是上海週末遊玩的好去處,可以根據自己的興趣和需求選擇合適的行程。"}, {"role":"user","content": "上海有哪些美食"}
Spring AI 適配了主流大模型,有不同的角色供使用:
在 Spring AI 程式碼中對應不同的列舉值:
public enum MessageType { USER("user"), ASSISTANT("assistant"), SYSTEM("system"), FUNCTION("function"); }
3.2.2 提示詞模板
在 Spring AI 與大模型互動的過程中,處理提示詞的方式與 Spring MVC 中管理“檢視View”的方式有些相似。首先要建立包含動態內容佔位符的模板,然後,這些佔位符會根據使用者請求或應用程式中的其他程式碼進行替換。另一個類比是JdbcTemplate中的語句,它包含可動態替換的佔位符。
下面是一個使用提示詞模板的簡單例子,在提示詞模板中,{佔位符} 可以用 Map 中的變數動態替換。
PromptTemplate promptTemplate = new PromptTemplate("Tell me a {adjective} joke about {topic}"); Prompt prompt = promptTemplate.create(Map.of("adjective", adjective, "topic", topic)); return chatClient.call(prompt).getResult();
3.2.3 程式碼示例
下面一段程式碼展示瞭如果用提示詞模板,生成一個旅遊助手,實現非常簡單,注意其中 SystemPromptTemplate 的實現。
@RestController public class AiController { @Autowired private OpenAiChatClient chatClient; @GetMapping("/ai/prompt") public Response prompt(@RequestParam(value = "name") String name, @RequestParam(value = "voice") String voice) { String userText = """ 給我推薦上海的至少三個旅遊景點 """; Message userMessage = new UserMessage(userText); String systemText = """ 你是一個有用的人工智慧助手,可以幫助人們查詢資訊, 你的名字是{name}, 你應該用你的名字和{voice}的風格回覆使用者的請求。 """; SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemText); Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice)); Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); List<Generation> response = chatClient.call(prompt).getResults(); StringBuilder result = new StringBuilder(); for (Generation generation : response) { String content = generation.getOutput().getContent(); result.append(content); } return Response.ok(result); } }
呼叫示例:
localhost:8080/ai/prompt?name=小王&voice=幽默 { "errno": 0, "msg": "success", "data": "嘿,我是小王!上海是一個充滿魅力的城市,有很多值得一遊的景點。我給你推薦三個:\n\n1. 東方明珠塔:作為上海的標誌性建築,東方明珠塔是上海的地標之一。你可以乘坐電梯到達塔頂,欣賞整個上海城市的美景。\n\n2. 外灘:外灘是上海最著名的觀光區之一,這裏有許多歐洲風格的建築,可以欣賞到黃浦江兩岸的美景,尤其在夜晚燈火輝煌。\n\n3. 田子坊:這裏是上海的老街區,保留了許多傳統的建築和文化。你可以在這裏漫步,感受上海的歷史和文化氛圍。\n\n希望你能在上海玩得開心!如果還有其他問題,隨時問我哦~" }
3.3 Function Calling
3.3.1 Function Calling概念
Function Calling 是大型語言模型連線外部的工具。大語言模型在處理任務時,可以透過 Function Calling 判斷是否需要引入外部工具以解決當前任務。Function Calling 的主要作用包括:
功能增強:透過函式呼叫,模型可以實現一些基本的文字生成能力之外的功能,如訪問資料庫、進行復雜的計算、生成圖片等;
提高效率:對於某些複雜的問題,直接在模型內部進行處理可能效率低下或不可行,透過外部函式呼叫可以利用專門的工具和演算法,提高處理效率;
互動增強:在一些應用場景中,如聊天機器人或助手技術,函式呼叫可以用來執行使用者的具體命令,比如設定提醒、查詢天氣等,使得互動更加自然和實用。
透過 Spring AI,可以在 Spring Boot 專案中輕鬆地使用大模型的 Function Calling 功能,向 Spring 容器中註冊一系列自定義 Java 函式,並讓大模型智慧地選擇需要呼叫哪些函式,以及讓大模型自動生成呼叫函式的入參(一個Json物件),從而將大模型功能與外部工具和API連線起來。大語言模型經過訓練,可以檢測何時應該呼叫函式,並使用符合函式簽名的Json進行響應。
注意,大模型不直接呼叫函式,相反,大模型生成Json,可以使用該Json來呼叫程式碼中的函式,並將函式結果返回給模型以完成對話。
△Spring AI Function Calling 實現原理
Spring AI 提供了靈活且使用者友好的方式來註冊和呼叫自定義函式。自定義函式需要提供函式名稱、描述和函式入參的結構體,這些描述有助於模型理解何時呼叫函式。
3.3.2 Function Calling程式碼示例
Step1:大模型無法獲得實時的天氣資訊,爲了增強模型功能,定義一個查詢天氣的工具(注意要透過Json註解描述清楚函式入參):
package com.baidu.mapp.lingjing.spring.ai.example.service; /** * 城市氣溫服務 */ 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 = "位置") @JsonPropertyDescription("城市,例如: 廣州") String location) { } /** * Weather Function response. */ public record Response(String weather) { } @Override public WeatherService.Response apply(WeatherService.Request request) { String weather = ""; if (request.location().contains("上海")) { weather = "小雨轉陰 13~19°C"; } else if (request.location().contains("深圳")) { weather = "陰 15~26°C"; } else { weather = "熱到中暑 39-40°C"; } return new WeatherService.Response(weather); } }
Step2:大模型無法獲得準確的人口資訊,爲了增強模型功能,再定義一個查詢人口的函式。
/** * 城市人口服務 */ public class PopulationService implements Function<PopulationService.Request, PopulationService.Response> { /** * Population Function request. */ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonClassDescription("Population API request") public record Request(@JsonProperty(required = true, value = "位置") @JsonPropertyDescription("城市,例如: 上海") String location) { } /** * Population Function response. */ public record Response(Integer population) { } @Override public PopulationService.Response apply(PopulationService.Request request) { Integer population = 0; if (request.location().contains("上海")) { population = 20000000; } else if (request.location().contains("深圳")) { population = 10000000; } else { population = 5000000; } return new PopulationService.Response(population); } }
Step3:將兩個服務封裝,生成Bean,注入Spring容器中(兩種型別均可,一種為FunctionCallback、一種為Function),注意要給函式加上描述,讓大模型理解函式的功能,以便能夠更好的觸發函式呼叫。
@Configuration public class ToolConfig { @Bean public FunctionCallback weatherFunctionInfo() { return FunctionCallbackWrapper.builder(new WeatherService()) .withName("currentWeather") .withDescription("獲取當地的氣溫") .build(); } @Bean @Description("獲取當地的人口") public Function<PopulationService.Request, PopulationService.Response> currentPopulation() { return new PopulationService(); } }
Step4:將兩個工具註冊在 Prompt 中,這樣 Spring AI 在呼叫大模型時會把函式資訊同時傳入,讓模型判斷是否呼叫。
@RestController public class AiController { @Autowired private OpenAiChatClient chatClient; @GetMapping("/ai/generate/function/call") public Response functionCall(@RequestParam(value = "message", defaultValue = "上海天氣如何?") String message) { String systemPrompt = "{prompt}"; SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt); Message systemMessage = systemPromptTemplate.createMessage(Map.of("prompt", "你是一個有用的人工智慧助手")); Message userMessage = new UserMessage(message); Prompt prompt = new Prompt(List.of(userMessage, systemMessage), OpenAiChatOptions.builder().withFunctions(Set.of("currentWeather", "currentPopulation")).build()); List<Generation> response = chatClient.call(prompt).getResults(); String result = ""; for (Generation generation : response) { String content = generation.getOutput().getContent(); result += content; } return Response.ok(result); } }
呼叫示例:
localhost:8080/ai/generate/function/call?message=上海的人口是多少 { "errno": 0, "msg": "success", "data": "上海的人口約為2000萬。" } localhost:8080/ai/generate/function/call?message=上海的天氣怎麼樣 { "errno": 0, "msg": "success", "data": "上海目前的天氣是小雨轉陰,氣溫在13°C到19°C之間。" }
3.4 結構化結果輸出
3.4.1 結構化輸出概念 OutputParser
大模型的輸出結果隨機性非常強,在建立AI原生應用時,爲了更好的適配我們展現樣式,往往需要輸出結果更具有標準的結果,比如按照我們規定的Json結構輸出。Spring AI 中的 OutputParser 類就可以實現結構化的輸出,比如將大模型的基於字串的輸出對映到Java類或陣列。可以將其視為與Spring JDBC的 RowMapper 或 ResultSetExtractor 概念類似的東西。
3.4.2 程式碼示例
下面一段程式碼展示了 OutputParser 的使用,可以將大模型的輸出直接對映為我們期望的 Java 類 ActorsFilms 的物件。
@Data public class ActorsFilms { private String actor; private List<String> movies; }
@RestController public class AiController { @Autowired private OpenAiChatClient chatClient; @GetMapping("/ai/parser") public Response Response(@RequestParam(value = "actor") String actor) { BeanOutputParser<ActorsFilms> outputParser = new BeanOutputParser<>(ActorsFilms.class); String userMessage = """ 為演員{actor}生成電影作品年表。 {format} """; logger.info("output format:{}", outputParser.getFormat()); PromptTemplate promptTemplate = new PromptTemplate(userMessage, Map.of("actor", actor, "format", outputParser.getFormat())); Prompt prompt = promptTemplate.create(); Generation generation = chatClient.call(prompt).getResult(); ActorsFilms actorsFilms = outputParser.parse(generation.getOutput().getContent()); return Response.ok(JacksonUtil.toJson(actorsFilms)); } }
原理:實際Spring AI在呼叫大模型時,會給模型輸入的prompt中加入指定輸出結果的格式。
Your response should be in JSON format. Do not include any explanations, only provide a RFC8259 compliant JSON response following this format without deviation. Do not include markdown code blocks in your response. Here is the JSON Schema instance your output must adhere to: ```{ "$schema" : "https://json-schema.org/draft/2020-12/schema", "type" : "object", "properties" : { "actor" : { "type" : "string" }, "movies" : { "type" : "array", "items" : { "type" : "string" } } } }
呼叫示例:
localhost:8080/ai/parser?actor=沈騰 { "errno": 0, "msg": "success", "data": { "actor": "沈騰", "movies": [ "德魯納酒店 (2022)", "奪冠 (2020)", "誤殺 (2019)", "飛馳人生 (2019)", "瘋狂的外星人 (2019)", "西虹市首富 (2018)", "羞羞的鐵拳 (2017)", "夏洛特煩惱 (2015)" ] } }
3.5 圖片生成
文生圖能力是大模型的基礎能力之一,例如百度的【文心一格】就可以根據使用者的要求,生成好看的圖片。Spring AI 中的影象生成 API 被設計成一個簡單且便攜的介面,用於與各種專門用於影象生成的 AI 模型進行互動,允許開發者透過最少的程式碼在不同影象生成的模型之間切換。這種設計符合 Spring 的模組化和互換性理念,確保開發者能夠快速地調整其應用程式,以適應各種與影象處理相關的AI功能。
程式碼示例:
@RestController public class AiController { @Autowired private ImageClient imageClient; /** * 圖片生成 * * @param description * @return */ @GetMapping("/ai/image") public Response image(@RequestParam(value = "description") String description) { ImageResponse response = imageClient.call( new ImagePrompt(description, OpenAiImageOptions.builder().withQuality("hd").withN(1).withHeight(1024).withWidth(1024).build())); return Response.ok(response.getResults().get(0).getOutput().getUrl()); } }
呼叫示例:
localhost:8080/ai/image?description=給我生成一張塞爾達的桌布
3.6 向量化
衆所周知,計算機無法讀懂自然語言,只能處理數值,因此自然語言需要以一定的形式轉化為數值。向量化就是將自然語言中的詞語對映為數值的一種方式。然而對於豐富的自然語言來說,將它們對映為數值向量,使之包含更豐富的語義資訊和抽象特徵顯然是一種更好的選擇。嵌入是浮點數的向量(列表),兩個向量之間的距離衡量它們的相關性,小距離表示高相關性,大距離表示低相關性。
向量化通常用於:
搜尋(結果按與查詢字串的相關性排序)
聚類(其中文字字串按相似性分組)
推薦(推薦具有相關文字字串的專案)
異常檢測(識別出相關性很小的異常值)
多樣性測量(分析相似性分佈)
分類(其中文字字串按其最相似的標籤分類)
向量化可以將單詞或短語表示為低維向量,這些向量具有豐富的語義資訊,可以捕捉單詞或短語的含義和上下文關係。
Embedding Client 旨在將大模型中的向量化功能直接整合。它的主要功能是將文字轉換為數字向量,通常稱為向量化。向量化對於實現各種功能,如語義分析和文字分類,是至關重要的。
程式碼示例:
@RestController public class AiController { @Autowired private EmbeddingClient embeddingClient; @GetMapping("/ai/embedding") public Response embed(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { // 向量化 EmbeddingResponse embeddingResponse = this.embeddingClient.embedForResponse(List.of(message)); return Response.ok(embeddingResponse); } }
呼叫示例:
localhost:8080/ai/embedding?message=上海的人口是多少 { "errno": 0, "msg": "success", "data": { "metadata": { "completion-tokens": null, "total-tokens": 8, "model": "text-embedding-ada-002", "prompt-tokens": 8 }, "results": [ { "index": 0, "metadata": null, "output": [ 0.021796793, -0.0034369868, 0.01705836, -0.0049721096, -0.037595898, -0.0075360565, -0.0352851, 0.01757764, -0.030845253, ... ] } } }
3.7 向量資料庫
3.7.1 向量資料庫的概念
向量資料庫是一種特殊的資料庫型別,在AI原生應用中起著關鍵作用。
在向量資料庫中,查詢與傳統關係型資料庫有所不同。它不是執行精確匹配,而是執行相似性搜尋。當給定一個向量作為查詢時,向量資料庫會返回與查詢向量“相似”的向量。有關如何計算這種相似性,請參閱向量相似性相關文件,這裏不做詳細介紹。
向量資料庫用於將私有的資料與大模型整合。使用它們的第一步是將您的資料載入到向量資料庫中。然後,當用戶的查詢要傳送到AI模型時,首先會檢索一組相似的文件。這些文件隨後將作為使用者問題的上下文,與使用者查詢一起傳送到大模型。這種技術被稱為檢索增強生成(RAG)。
Spring AI 支援整合的向量庫如下:
Azure Vector Search - The Azure vector store.
ChromaVectorStore - The Chroma vector store.
MilvusVectorStore - The Milvus vector store.
Neo4jVectorStore - The Neo4j vector store.
PgVectorStore - The PostgreSQL/PGVector vector store.
PineconeVectorStore - PineCone vector store.
QdrantVectorStore - Qdrant vector store.
RedisVectorStore - The Redis vector store.
WeaviateVectorStore - The Weaviate vector store.
SimpleVectorStore - A simple implementation of persistent vector storage, good for educational purposes.
3.7.2 寫入向量庫
寫入向量資料庫前,首先要將文字用大模型向量化,因此在 Spring AI 中向量資料庫與向量化方法是繫結在一起使用的。
△向量資料集寫入原理
程式碼示例:
生成向量庫 Bean,VectorStore Bean 的建立條件是 上一小節中的 EmbeddingClient
@Configuration public class ToolConfig { /** * 生成向量庫Bean * * @param embeddingClient * @return */ @Bean public VectorStore createVectorStore(EmbeddingClient embeddingClient) { return new SimpleVectorStore(embeddingClient); } }
寫入向量庫(包括向量化與寫入向量庫兩步)
@RestController public class AiController { @Autowired private VectorStore vectorStore; @GetMapping("/ai/vectorStore/add") public Response vectorStoreAdd(@RequestParam(value = "content") String content) { List<Document> documentList = new ArrayList<>(); Document document = new Document(content); documentList.add(document); vectorStore.add(documentList); return Response.ok(); } }
3.7.3 檢索向量庫
檢索向量庫時,首先將 query 詞向量化,再去向量庫中匹配向量結果距離最短的向量,獲取其對應的文字段輸出。Spring AI 將整個流程封裝為 similaritySearch 方法。
程式碼示例:
@RestController public class AiController { @Autowired private VectorStore vectorStore; @GetMapping("/ai/vectorStore/search") public Response vectorStoreSearch(@RequestParam(value = "query") String query) { List<Document> documents = vectorStore.similaritySearch(query); return Response.ok(JacksonUtil.toJson(documents)); } }
呼叫示例:
# 寫入三條文字到向量庫 localhost:8080/ai/vectorStore/add?content=深圳的天氣熱 localhost:8080/ai/vectorStore/add?content=北京的天氣晴 localhost:8080/ai/vectorStore/add?content=上海的天氣陰 # 向量庫檢索,可以看到相似性越高,返回順序越靠前 localhost:8080/ai/vectorStore/search?query=上海的天氣如何 { "errno": 0, "msg": "success", "data": [ "上海的天氣陰", "北京的天氣晴", "深圳的天氣熱" ] }
04 RAG 檢索增強生成
4.1 RAG 解決的問題
大模型雖然很強大,但直接使用有幾個問題有待解決:它們經常捏造事實,在處理特定領域或高度專業化的查詢時缺乏知識。例如,當所尋求的資訊超出模型的訓練資料範圍或需要最新資料時,大語言模型可能無法提供準確的答案。這一限制在將生成式人工智慧部署到現實世界的生產環境中時構成挑戰,因為單純依賴一個不透明的大語言模型可能不夠。
檢索增強生成(Retrieval-Augmented Generation,RAG)技術旨在解決將外部資料輸入納入提示詞以獲取準確的大模型響應。
RAG 為大語言模型提供從某些資料來源檢索到的資訊,作為其生成答案的依據。RAG 是模型基於搜索到的資訊作為上下文進行回答。查詢和檢索到的上下文都被注入到傳送給大語言模型的提示詞中。RAG 是 2023-2024 年最流行的基於 LLM 的系統架構。有許多產品幾乎完全基於 RAG 構建 - 從將網路搜索引擎與 LLM 相結合的問答服務到數百個chat-with-your-data 應用程式。
4.2 RAG 的原理
下面這張圖很好的解釋了一個外部 PDF 檔案如果作為大模型的知識補充。
△一個最簡單的 RAG 的原理
這種方法涉及流式程式設計模型,首先要從文件中讀取非結構化資料,對其進行轉換,變為結構化的資料,然後向量化,再將其寫入向量資料庫。從高層次來看,這是一個ETL(提取、轉換和載入)的pipe。在 RAG 技術的檢索部分中,也使用了向量資料庫。
將非結構化資料載入到向量資料庫時,最重要的轉換之一是將原始文件拆分成較小的部分(大模型的輸入 token 數有限,將全文全部輸入給模型不現實,只能將最相關的部分輸入模型)。將原始文件拆分成較小部分的過程包括兩個重要步驟:
在保留內容語義邊界的同時,將文件拆分成多個部分。對於包含段落和表格的文件,應避免在段落或表格中間拆分文件。對於程式碼,應避免在方法實現中間拆分程式碼。要求被拆分成為的每一個文字塊都佔大模型模型輸入 token 限制很小的一部分。
RAG 的下一個階段是處理使用者輸入。當大模型需要回答使用者的問題時,將問題和所有“相似”的文字塊放入傳送給大模型的提示詞。這就是使用向量資料庫的原因,它非常擅長查詢相似的內容,向量資料庫的寫入與檢索在上一章節中已經介紹。
4.3 生成一個 RAG 知識庫
生成一個 RAG 知識庫包括四步:檔案解析、文字切分、段落向量化、入向量庫。
以下是一個程式碼示例,把一份簡歷寫入向量庫:
教育經歷
蘇州大學 船舶電氣工程技術 本科 2011.09 - 2015.06
主修課程:電工技術、電子技術、電機與電力拖動、微機原理、可程式設計控制器(PLC)、電氣照明技術、船舶概論、現代檢測技術、船舶電氣裝置及系統、船舶輔機電氣控制系統、船舶裝置安全與管理、航海儀器、船舶通訊與導航、微機控制系統、電路、電子基礎、電機及拖動、可程式設計序控制器、微控制器技術、船舶電氣施工工藝、船機拖動控制系統、船舶機艙自動控制系統。
實習經歷
蘇州小優智慧科技有限公司 演算法工程師
2015-4-1 - 2017-9-1
擁有2年以上的演算法工程師經驗,熟悉數據結構、演算法及機器學習模型的實現與最佳化,在多個專案中負責演算法模型的設計與開發,包括基於深度學習的影象識別、語音識別及自然語言處理等方向。熟練使用TensorFlow、PyTorch等主流深度學習框架,能獨立開發出高質量、高效能的演算法模型。
在前一份工作中擔任演算法工程師,負責開發基於深度學習的推薦演算法,成功實現了推薦效果的提升,提升了公司的使用者留存率和收益。在此之前,還參與過一些資料建模和預測的研究專案,在工作中積累了數據處理和建模的經驗。
曾在一家創業公司實習,負責開發基於深度強化學習的智慧對話系統。在專案中,擔任小組負責人並領導團隊順利完成了對話模型的設計和實現。透過專案,熟練掌握了深度強化學習等先進人工智慧技術,並具備解決自然語言處理領域複雜問題的能力。
在研究生期間,參加了一項基於影象識別的研究專案,並擔任專案組長。在研究中,使用深度摺積神經網路實現了影象的分類和識別,在國內外學術會議上獲得了多個論文發表和報告的機會,積累了學術研究和成果展示的經驗。
陝西歐卡電子智慧科技有限公司 演算法工程師
2018-10-1 - 2020-3-1
擔任ABC公司演算法工程師,負責參與開發高效能機器學習演算法。在專案中,我使用Python和MATLAB編寫了多種演算法模型,並且實現了GPU加速計算,使得演算法在處理大規模複雜資料時表現優異。
就職於DEF科技公司,作為演算法工程師,全程參與了一款自動駕駛系統的開發。在專案中,我主要透過深度學習、目標檢測等技術,實現了車輛識別、道路分割等多項技術難點,使得系統在真實道路環境下表現出了較高的穩定性和可靠性。
在GHI軟體公司,我作為演算法工程師負責了一項推薦系統的研發。該系統基於使用者行為資料,使用協同過濾和深度學習技術,為使用者推薦最優質的內容。在專案中,我最佳化了多種推薦演算法,最佳化推薦精度達到了90%以上。
曾就職於JKL醫療科技公司,作為演算法工程師負責開發醫療影像診斷平臺。在專案中,我使用深度學習技術對醫學影象進行自動分析和診斷,提高了醫生的工作效率和診斷準確率。
專案經驗
蘇州小優智慧科技有限公司相關專案 蘇州小優智慧科技有限公司 演算法工程師
2016-4-1 - 2016-9-1
開發基於協同過濾的音樂推薦系統,主要負責資料清洗和模型訓練。使用Python實現了基於使用者相似度和物品相似度的協同過濾演算法,並應用到推薦系統中,提高了推薦準確度。
參與人工智慧智慧城市專案,主要工作為基於使用者畫像的廣告推薦系統開發。利用Spark進行大規模數據處理和分析,採用深度學習神經網路構建使用者畫像,並實現了召回模型和排序模型的訓練,提升推薦效果。
參與線上教育平臺數據挖掘和推薦系統開發,主要工作為開發基於LBS和時間因素的影片推薦系統。使用Spark Streaming進行實時數據處理,利用Hadoop進行離線數據處理,應用LDA模型進行主題關鍵字提取和話題挖掘,從而提高了推薦準確性。
參與電商平臺熱銷商品推薦系統開發,主要工作為開發基於神經網路和深度強化學習的推薦演算法。使用TensorFlow進行模型訓練和預測,應用異構網路結構和實時監控技術,提高了推薦準確度和實時性。
歐卡電子相關專案 陝西歐卡電子智慧科技有限公司 演算法工程師
2019-10-1 - 2020-3-1
開發和最佳化影象識別演算法,提高識別的準確率和速度。利用OpenCV、TensorFlow等開源工具,結合人工智慧和深度學習技術,開發識別系統,並持續進行效能最佳化;
開發影象處理演算法,包括去噪、模糊、增強等多種演算法,達到提高影象質量、增強細節等效果,並應用於實際場景;
參與人臉識別專案,利用深度學習技術進行人臉檢測、特徵提取和比對,提高人臉識別的準確率;
參與影象翻譯專案,開發適用於不同語種的OCR演算法,將影象上的文字轉換成文字,並進行翻譯,為使用者提供更智慧的翻譯服務;
學校實踐
機器學習演算法實驗課程實踐:透過學習機器學習的相關理論知識,瞭解推薦演算法的原理,並實踐了基於協同過濾演算法的電影推薦系統的開發。
資料庫系統實踐:學習資料庫系統的概念和原理,並透過MySQL的實踐操作,完成了推薦演算法中的資料儲存與查詢操作,並對系統的效能進行了最佳化。
資料探勘與大資料實踐:透過使用Python程式語言和相關工具,熟練掌握了資料預處理、特徵選擇、模型訓練與評估等資料探勘的基本步驟,並實踐了基於協同過濾演算法的使用者推薦系統的開發。
技能特長
影象處理技術:具備影象處理基礎知識,熟練使用OpenCV等影象處理庫進行影象處理、特徵提取等操作。
深度學習:熟練使用TensorFlow、PyTorch等深度學習框架,瞭解神經網路理論,掌握CNN、RNN、GAN等深度學習演算法。
數據結構與演算法:熟悉數據結構和基本演算法,能夠靈活運用演算法進行解決問題。
語言技能:熟練掌握Python、C++等程式語言,有良好的程式設計習慣和程式碼風格,能夠快速上手新的程式語言和工具。
專案經驗:熟練掌握常用的影象演算法,並在實踐中有多個專案實踐經驗,包括但不限於影象分割、目標檢測、人臉識別等專案。
△劉磊簡歷
@RestController public class AiController { @Autowired private VectorStore vectorStore; @GetMapping("/ai/rag/create") public Response ragCreate() { // 1. 提取文字內容 String filePath = "劉磊簡歷.txt"; TextReader textReader = new TextReader(filePath); textReader.getCustomMetadata().put("filePath", filePath); List<Document> documents = textReader.get(); logger.info("documents before split:{}", documents); // 2. 文字切分為段落 TextSplitter splitter = new TokenTextSplitter(1200, 350, 5, 100, true); documents = splitter.apply(documents); logger.info("documents after split:{}", documents); // 3. 段落寫入向量資料庫 vectorStore.add(documents); return Response.ok(); } }
向量庫中的幾個段落包括:
[ Document{id='80076c55-bc3b-4760-bcda-a454da3da835', metadata={charset=UTF-8, filePath=劉磊簡歷.txt, source=劉磊簡歷.txt}, content='教育經歷 蘇州大學 船舶電氣工程技術 本科 2011.09 - 2015.06 主修課程:電工技術、電子技術、電機與電力拖動、微機原理、可程式設計控制器(PLC)、電氣照明技術、船舶概論、現代檢測技術、船舶電氣裝置及系統、船舶輔機電氣控制系統、船舶裝置安全與管理、航海儀器、船舶通訊與導航、微機控制系統、電路、電子基礎、電機及拖動、可程式設計序控制器、微控制器技術、船舶電氣施工工藝、船機拖動控制系統、船舶機艙自動控制系統。 實習經歷 蘇州小優智慧科技有限公司 演算法工程師 2015-4-1 - 2017-9-1 擁有2年以上的演算法工程師經驗,熟悉數據結構、演算法及機器學習模型的實現與最佳化,在多個專案中負責演算法模型的設計與開發,包括基於深度學習的影象識別、語音識別及自然語言處理等方向。熟練使用TensorFlow、PyTorch等主流深度學習框架,能獨立開發出高質量、高效能的演算法模型。 在前一份工作中擔任演算法工程師,負責開發基於深度學習的推薦演算法,成功實現了推薦效果的提升,提升了公司的使用者留存率和收益。在此之前,還參與過一些資料建模和預測的研究專案,在工作中積累了數據處理和建模的經驗。 曾在一家創業公司實習,負責開發基於深度強化學習的智慧對話系統。在專案中,擔任小組負責人並領導團隊順利完成了對話模型的設計和實現。透過專案,熟練掌握了深度強化學習等先進人工智慧技術,並具備解決自然語言處理領域複雜問題的能力。 在研究生期間,參加了一項基於影象識別的研究專案,並擔任專案組長。在研究中,使用深度摺積神經網路實現了影象的分類和識別,在國內外學術會議上獲得了多個論文發表和報告的機會,積累了學術研究和成果展示的經驗。 陝西歐卡電子智慧科技有限公司 演算法工程師 2018-10-1 - 2020-3-1 擔任ABC公司演算法工程師,負責參與開發高效能機器學習演算法。在專案中,我使用Python和MATLAB編寫了多種演算法模型,並且實現了GPU加速計算,使得演算法在處理大規模複雜資料時表現優異。 就職於DEF科技公司,作為演算法工程師,全程參與了一款自動駕駛系統的開發。在專案中,我主要透過深度學習、目標檢測等技術,實現了車輛識別、道路分割等多項技術難點,使得系統在真實道路環境下表現出了較高的穩定性和可靠性。 在GHI軟體公司,我作為演算法工程師負責了一項推薦系統的研發。該系統基於使用者行為資料,使用協同過濾和深度學習技術,為使用者推薦最優質的內容。在專案中,我最佳化了多種推薦演算法,最佳化推薦精度達到了90%以上。'}, Document{id='cbc4f2ac-e71f-4d92-821a-b2f51b8212a8', metadata={charset=UTF-8, filePath=劉磊簡歷.txt, source=劉磊簡歷.txt}, content='劉磊曾就職於JKL醫療科技公司,作為演算法工程師負責開發醫療影像診斷平臺。在專案中,我使用深度學習技術對醫學影象進行自動分析和診斷,提高了醫生的工作效率和診斷準確率。 專案經驗 蘇州小優智慧科技有限公司相關專案 蘇州小優智慧科技有限公司 演算法工程師 2016-4-1 - 2016-9-1 開發基於協同過濾的音樂推薦系統,主要負責資料清洗和模型訓練。使用Python實現了基於使用者相似度和物品相似度的協同過濾演算法,並應用到推薦系統中,提高了推薦準確度。 參與人工智慧智慧城市專案,主要工作為基於使用者畫像的廣告推薦系統開發。利用Spark進行大規模數據處理和分析,採用深度學習神經網路構建使用者畫像,並實現了召回模型和排序模型的訓練,提升推薦效果。 參與線上教育平臺數據挖掘和推薦系統開發,主要工作為開發基於LBS和時間因素的影片推薦系統。使用Spark Streaming進行實時數據處理,利用Hadoop進行離線數據處理,應用LDA模型進行主題關鍵字提取和話題挖掘,從而提高了推薦準確性。 參與電商平臺熱銷商品推薦系統開發,主要工作為開發基於神經網路和深度強化學習的推薦演算法。使用TensorFlow進行模型訓練和預測,應用異構網路結構和實時監控技術,提高了推薦準確度和實時性。 歐卡電子相關專案 陝西歐卡電子智慧科技有限公司 演算法工程師 2019-10-1 - 2020-3-1 開發和最佳化影象識別演算法,提高識別的準確率和速度。利用OpenCV、TensorFlow等開源工具,結合人工智慧和深度學習技術,開發識別系統,並持續進行效能最佳化; 開發影象處理演算法,包括去噪、模糊、增強等多種演算法,達到提高影象質量、增強細節等效果,並應用於實際場景; 參與人臉識別專案,利用深度學習技術進行人臉檢測、特徵提取和比對,提高人臉識別的準確率; 參與影象翻譯專案,開發適用於不同語種的OCR演算法,將影象上的文字轉換成文字,並進行翻譯,為使用者提供更智慧的翻譯服務; 學校實踐 機器學習演算法實驗課程實踐:透過學習機器學習的相關理論知識,瞭解推薦演算法的原理,並實踐了基於協同過濾演算法的電影推薦系統的開發。 資料庫系統實踐:學習資料庫系統的概念和原理,並透過MySQL的實踐操作,完成了推薦演算法中的資料儲存與查詢操作,並對系統的效能進行了最佳化。 資料探勘與大資料實踐:透過使用Python程式語言和相關工具,熟練掌握了資料預處理、特徵選擇、模型訓練與評估等資料探勘的基本步驟,並實踐了基於協同過濾演算法的使用者推薦系統的開發。'}, Document{id='e37661a2-aeb7-4fa6-ae29-696fd1f03baa', metadata={charset=UTF-8, filePath=劉磊簡歷.txt, source=劉磊簡歷.txt}, content='劉磊語言技能:熟練掌握Python、C++等程式語言,有良好的程式設計習慣和程式碼風格,能夠快速上手新的程式語言和工具。 專案經驗:熟練掌握常用的影象演算法,並在實踐中有多個專案實踐經驗,包括但不限於影象分割、目標檢測、人臉識別等專案。 技能特長 影象處理技術:具備影象處理基礎知識,熟練使用OpenCV等影象處理庫進行影象處理、特徵提取等操作。 深度學習:熟練使用TensorFlow、PyTorch等深度學習框架,瞭解神經網路理論,掌握CNN、RNN、GAN等深度學習演算法。 數據結構與演算法:熟悉數據結構和基本演算法,能夠靈活運用演算法進行解決問題。 '}]
注:簡歷資訊為虛擬
這樣,一個簡單的 RAG 知識庫就建立完成了,使用時直接在向量庫中檢索即可。
05 搭建一個AI原生應用
5.1 AI原生應用的組成部分
有了 Spring AI 的幫助,我們就可以輕鬆的搭建一個簡單的AI原生應用了。
AI應用除了依賴大模型外,還有三個更為重要的組成部分:人設、知識庫與工具,分別對應上文中介紹的 Prompt、RAG 與 Function Calling。
人設是應用的設定,要描述你的應用要實現什麼目標。
知識庫能在大模型基礎上解決以下問題:
在不需要對模型進行調優的情況下可以保證模型回答結果的專業性;
大模型在回答問題時可以依賴資料集進行推理與回答,減少錯誤的可能性;
補充現有模型通用知識,可以確保回答的準確性與時效性。
工具能在大模型基礎上解決以下問題:如果說大模型是一個智慧中樞大腦,工具就是大模型的耳、目、手。工具將大模型的AI能力與外部應用相結合,既能豐富大模型的能力和應用場景,也能利用大模型的生成能力完成此前無法實現的任務。
△一個簡單的AI智慧體
5.2 一個簡單的AI原生應用
下面我們來搭建一個AI原生應用,這個應用可以用了快速檢視應聘候選人的資訊及與候選人崗位的匹配度。
step1:首先用全部候選人的簡歷構建一個簡歷知識庫,具體構建方式我們在4.3章節中已經介紹。
step2:建立一個工具(Function),可以用來查詢候選人應聘的崗位。
/** * 招聘投遞服務 */ public class RecruitService implements Function<RecruitService.Request, RecruitService.Response> { /** * Recruit Function request. */ @JsonInclude(JsonInclude.Include.NON_NULL) @JsonClassDescription("Recruit API request") public record Request(@JsonProperty(required = true, value = "人名") @JsonPropertyDescription("投遞簡歷人姓名,例如: 張真源") String name) { } /** * Recruit Function response. */ public record Response(String position) { } @Override public RecruitService.Response apply(RecruitService.Request request) { String position = "未知"; if (request.name().contains("張真源")) { position = "演算法工程師"; } return new RecruitService.Response(position); } }
step3:編寫應用的人設。
角色與目標:你是一個招聘助手,會針對使用者的問題,結合候選人經歷,崗位匹配度等專業知識,給使用者提供指導。 指導原則:你需要確保給出的建議合理且科學,不會對候選人的表現有侮辱言論。 限制:在提供建議時,需要強調在個性化建議方面使用者仍然需要線下尋求專業諮詢。 澄清:在與使用者互動的過程中,你需要明確回答使用者關於招聘方面的問題,對於非招聘相關的問題,你的迴應是“我只是一個招聘助手,不能回答這個問題噢”。 個性化:在回答時,你需要以專業、可靠的語氣回答,偶爾也可以帶些風趣和幽默,調節氛圍。
step4:將人設、知識庫、工具透過 Spring AI 框架串聯起了,搭建成應用。
@RestController public class AiController { @Autowired private VectorStore vectorStore; /** * 根據RAG回答問題 * * @param query * @return */ @GetMapping("/ai/agent") public Response rag(@RequestParam(value = "query") String query) { // 首先檢索掛載資訊 List<Document> documents = vectorStore.similaritySearch(query); // 提取最相關的資訊 String info = ""; if (documents.size() > 0) { info = documents.get(0).getContent(); } // 構造系統prompt String systemPrompt = """ 角色與目標:你是一個招聘助手,會針對使用者的問題,結合候選人經歷,崗位匹配度等專業知識,給使用者提供指導。 指導原則:你需要確保給出的建議合理且科學,不會對候選人的表現有侮辱言論。 限制:在提供建議時,需要強調在個性化建議方面使用者仍然需要線下尋求專業諮詢。 澄清:在與使用者互動的過程中,你需要明確回答使用者關於招聘方面的問題,對於非招聘相關的問題,你的迴應是“我只是一個招聘助手,不能回答這個問題噢”。 個性化:在回答時,你需要以專業、可靠的語氣回答,偶爾也可以帶些風趣和幽默,調節氛圍。 給你提供一些資料參考,並且給你呼叫崗位投遞檢索工具 請你跟進資料參考與工具返回結果回覆使用者的請求。 """; // 構造使用者prompt String userPrompt = """ 給你提供一些資料參考: {info},請回答我的問題:{query} 請你跟進資料參考與工具返回結果回覆使用者的請求。 """; // 構造提示詞 Message systemMessage = new SystemMessage(systemPrompt); PromptTemplate promptTemplate = new PromptTemplate(userPrompt); Message userMessage = promptTemplate.createMessage(Map.of("info", info, "query", query)); Prompt prompt = new Prompt(List.of(userMessage, systemMessage), OpenAiChatOptions.builder().withFunctions(Set.of("recruitPosition")).build()); // 呼叫大模型回答問題 List<Generation> response = chatClient.call(prompt).getResults(); StringBuilder result = new StringBuilder(); for (Generation generation : response) { String content = generation.getOutput().getContent(); result.append(content); } return Response.ok(result); } }
呼叫示例:
localhost:8080/ai/rag?query=劉磊是否有資格參與面試 ### 返回結果 當然,劉磊完全有資格參與演算法工程師的面試。他擁有船舶電氣工程技術的本科學歷,並且在演算法工程師這個職位上積累了豐富的工作經驗。 從實習經歷來看,他在蘇州小優智慧科技有限公司和陝西歐卡電子智慧科技有限公司都擔任了演算法工程師的角色,並且在多個專案中成功實現了演算法模型的設計與開發,涉及了深度學習、影象識別、語音識別及自然語言處理等多個方向。這些經驗顯示了他不僅熟悉數據結構和演算法,還熟練掌握了TensorFlow、PyTorch等主流深度學習框架,以及Python和MATLAB等程式語言。 特別是,他在自動駕駛系統開發、推薦系統研發等領域的經驗,都表明他具備解決複雜問題的能力,並且在實際應用中取得了顯著成果。這些經驗和成果都將是他在面試中展現自己能力和優勢的重要資本。 因此,我強烈建議給予劉磊面試的機會,以便更全面地瞭解他的能力和潛力。當然,面試結果還需要結合公司的具體需求和崗位匹配度來綜合評估。
——————END——————
作者:百度Geek說
連結:https://juejin.cn/post/7376837003521097740