SpringAI?Agent開發(fā)秘籍如何讓javaer也可以用上Agent?Skills
要說最近AI相關(guān)話題中什么最火,毫無疑問是Claude Skills,讓我感到震驚的倒不是它為什么火爆,而是SpringAI居然已經(jīng)迅速支持上Skills了,這效率真的是堪比??了。
誰說AI時(shí)代java開發(fā)者要掉隊(duì)了? 肉雖然不一定吃得上,但是喝口湯還是妥妥的
接下來我們通過構(gòu)建一個(gè)code reviewer, 來實(shí)際體驗(yàn)一把,如何將SpringAI和Skills結(jié)合起來使用
一、項(xiàng)目創(chuàng)建
1. 基礎(chǔ)環(huán)境要求
要體驗(yàn)SpringAI & Skills,目前需要升級(jí)到SpringAI 2.x版本,同時(shí)我們的SpringBoot也可以升級(jí)到4.x
- SpringAI: 2.0.0-M2
- JDK21
- SpringBoot: 4.0.1
除了這幾個(gè)基本依賴之外,我們可以選擇一個(gè)支持Function Tool的大模型來作為這個(gè)實(shí)現(xiàn)的大腦中樞
我們這里選擇智譜的大模型GLM-4.5-Flash (原因就是因?yàn)樗赓M(fèi),且效果還行,對(duì)所有想體驗(yàn)的小伙伴沒有任何額外成本投入)
2. 項(xiàng)目創(chuàng)建
接下來我們創(chuàng)建一個(gè)SpringAI應(yīng)用,對(duì)于一個(gè)標(biāo)準(zhǔn)的SpringAI應(yīng)用,在pom.xml配置中,你會(huì)看到下面這些基礎(chǔ)版本指定,這個(gè)也沒什么好說的
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.1</version>
<relativePath />
</parent>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-ai.version>2.0.0-M2</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</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>
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>接下來重點(diǎn)看一下我們這個(gè)項(xiàng)目所用到的幾個(gè)核心依賴
<dependencies>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-agent-utils</artifactId>
<version>0.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
</dependencies>- spring-ai-agent-utils: 這個(gè)就是SpringAI進(jìn)行agent開發(fā)的關(guān)鍵依賴包
- spring-ai-starter-model-zhipuai: 這個(gè)是智譜大模型進(jìn)行交互的依賴包
3. 項(xiàng)目配置
依賴搞定之后,接下來就是在配置文件中,配置LLM訪問的相關(guān)信息、以及agent相關(guān)配置參數(shù),對(duì)應(yīng)的配置文件 resources/application.yml
spring:
ai:
zhipuai:
# api-key 使用你自己申請(qǐng)的進(jìn)行替換;如果為了安全考慮,可以通過啟動(dòng)參數(shù)進(jìn)行設(shè)置
api-key: ${zhipuai-api-key}
chat: # 聊天模型
options:
model: GLM-4.5-Flash
## Agent Configuration
agent:
skills:
dirs: classpath:/.claude/skills
model: GLM-4.5-Flash這幾個(gè)配置看起來和之前SpringAI相關(guān)的并沒有太多的區(qū)別,其中 agent 相關(guān)的配置中,主要設(shè)置了skills的存放路徑,使用的model
根據(jù)上面的定義,我們將skills信息,放在resources/.claude/skills目錄下
新增一個(gè)目錄code-reviewer,目錄下的文件為 SKILL.md
.claude/skills/code-reviewer/ └── SKILL.md
對(duì)應(yīng)的內(nèi)容如下
--- name: code-reviewer description: Reviews Java code for best practices, security issues, and Spring Framework conventions. Use when user asks to review, analyze, or audit code. --- # Code Reviewer ## Instructions 在審查代碼時(shí): 1. 檢查是否存在安全漏洞(如SQL注入、XSS等) 2. 驗(yàn)證是否遵循了Spring Boot的最佳實(shí)踐(如正確使用@Service、@Repository等注解) 3. 查找潛在的空指針異常 4. 提出提高代碼可讀性和可維護(hù)性的建議 5. 提供具體的逐行反饋,并附上代碼示例 6. 以中文的方式返回代碼評(píng)審結(jié)果
4. Skills簡要說明
我們上面的Skill比較簡單,就是一個(gè)markdown文檔,SpringAI支持的Skills中,除了包含基本的SKILL.md文件(包含元數(shù)據(jù)(名稱和描述)以及指導(dǎo)代理如何執(zhí)行特定任務(wù)的說明)之外,還可以有相關(guān)的腳本、模板和參考資料
一個(gè)常見的skills結(jié)構(gòu)如下
my-skill/ ├── SKILL.md # Required: instructions + metadata ├── scripts/ # Optional: executable code ├── references/ # Optional: documentation └── assets/ # Optional: templates, resources
二、核心實(shí)現(xiàn)
現(xiàn)在前置準(zhǔn)備已經(jīng)完成,接下來開始正式的體驗(yàn)吧
2.1 交互日志打印 MyLoggingAdvisor
為了讓系統(tǒng)與大模型之間的交互更清晰,我們將雙方交互的日志進(jìn)行更友好的打?。ㄒ岔槺憧匆幌?,一次用戶感知的問答過程中,實(shí)際上有幾次交互)
public class MyLoggingAdvisor implements BaseAdvisor {
private final int order;
public final boolean showSystemMessage;
public final boolean showAvailableTools;
private AtomicInteger cnt = new AtomicInteger(1);
private MyLoggingAdvisor(int order, boolean showSystemMessage, boolean showAvailableTools) {
this.order = order;
this.showSystemMessage = showSystemMessage;
this.showAvailableTools = showAvailableTools;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
System.out.println("======================= 第 " + cnt.getAndAdd(1) + " 輪 ====================================");
StringBuilder sb = new StringBuilder("\nUSER: ");
if (this.showSystemMessage && chatClientRequest.prompt().getSystemMessage() != null) {
sb.append("\n - SYSTEM: ").append(first(chatClientRequest.prompt().getSystemMessage().getText(), 300));
}
if (this.showAvailableTools) {
Object tools = "No Tools";
if (chatClientRequest.prompt().getOptions() instanceof ToolCallingChatOptions toolOptions) {
tools = toolOptions.getToolCallbacks().stream().map(tc -> tc.getToolDefinition().name()).toList();
}
sb.append("\n - TOOLS: ").append(ModelOptionsUtils.toJsonString(tools));
}
Message lastMessage = chatClientRequest.prompt().getLastUserOrToolResponseMessage();
if (lastMessage.getMessageType() == MessageType.TOOL) {
ToolResponseMessage toolResponseMessage = (ToolResponseMessage) lastMessage;
for (var toolResponse : toolResponseMessage.getResponses()) {
var tr = toolResponse.name() + ": " + first(toolResponse.responseData(), 1000);
sb.append("\n - TOOL-RESPONSE: ").append(tr);
}
} else if (lastMessage.getMessageType() == MessageType.USER) {
if (StringUtils.hasText(lastMessage.getText())) {
sb.append("\n - TEXT: ").append(first(lastMessage.getText(), 1000));
}
}
System.out.println("before: " + sb);
return chatClientRequest;
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
StringBuilder sb = new StringBuilder("\nASSISTANT: ");
if (chatClientResponse.chatResponse() == null || chatClientResponse.chatResponse().getResults() == null) {
sb.append(" No chat response ");
System.out.println("after: " + sb);
return chatClientResponse;
}
for (var generation : chatClientResponse.chatResponse().getResults()) {
var message = generation.getOutput();
if (message.getToolCalls() != null) {
for (var toolCall : message.getToolCalls()) {
sb.append("\n - TOOL-CALL: ")
.append(toolCall.name())
.append(" (")
.append(toolCall.arguments())
.append(")");
}
}
if (message.getText() != null) {
if (StringUtils.hasText(message.getText())) {
sb.append("\n - TEXT: ").append(first(message.getText(), 1200));
}
}
}
System.out.println("after: " + sb);
return chatClientResponse;
}
private String first(String text, int n) {
if (text.length() <= n) {
return text;
}
return text.substring(0, n) + "...";
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private int order = 0;
private boolean showSystemMessage = true;
private boolean showAvailableTools = true;
public Builder order(int order) {
this.order = order;
return this;
}
public Builder showSystemMessage(boolean showSystemMessage) {
this.showSystemMessage = showSystemMessage;
return this;
}
public Builder showAvailableTools(boolean showAvailableTools) {
this.showAvailableTools = showAvailableTools;
return this;
}
public MyLoggingAdvisor build() {
MyLoggingAdvisor advisor = new MyLoggingAdvisor(this.order, this.showSystemMessage,
this.showAvailableTools);
return advisor;
}
}
}2.2 準(zhǔn)備用于評(píng)審的代碼
我們直接使用 實(shí)戰(zhàn) | 零基礎(chǔ)搭建知識(shí)庫問答機(jī)器人:基于SpringAI+RAG的完整實(shí)現(xiàn) 中的代碼分塊的內(nèi)容作為待評(píng)審的內(nèi)容,看下這段簡單的文本分塊工具會(huì)評(píng)審出什么內(nèi)容
package com.git.hui.springai.app.demo;
import org.springframework.ai.document.Document;
import java.util.ArrayList;
import java.util.List;
/**
* 文檔分塊工具類
* 將長文檔分割成較小的塊,以便更好地進(jìn)行向量化和檢索
*/
public class DocumentChunker {
private final int maxChunkSize;
private final int overlapSize;
public static DocumentChunker DEFAULT_CHUNKER = new DocumentChunker();
public DocumentChunker() {
this(500, 50); // 默認(rèn)值:最大塊大小500個(gè)字符,重疊50個(gè)字符
}
public DocumentChunker(int maxChunkSize, int overlapSize) {
this.maxChunkSize = maxChunkSize;
this.overlapSize = overlapSize;
}
/**
* 將文檔分割成塊
*
* @param document 輸入文檔
* @return 分割后的文檔塊列表
*/
public List<Document> chunkDocument(Document document) {
String content = document.getText();
if (content == null || content.trim().isEmpty()) {
return List.of(document);
}
List<String> chunks = splitText(content);
List<Document> chunkedDocuments = new ArrayList<>();
for (int i = 0; i < chunks.size(); i++) {
String chunk = chunks.get(i);
String chunkId = document.getId() + "_chunk_" + i;
// 創(chuàng)建新的文檔塊,保留原始文檔的元數(shù)據(jù)
Document chunkDoc = new Document(chunkId, chunk, new java.util.HashMap<>(document.getMetadata()));
// 添加塊相關(guān)的元數(shù)據(jù)
chunkDoc.getMetadata().put("chunk_index", i);
chunkDoc.getMetadata().put("total_chunks", chunks.size());
chunkDoc.getMetadata().put("original_document_id", document.getId());
chunkedDocuments.add(chunkDoc);
}
return chunkedDocuments;
}
/**
* 將文本分割成塊
*
* @param text 輸入文本
* @return 分割后的文本塊列表
*/
private List<String> splitText(String text) {
List<String> chunks = new ArrayList<>();
// 按多種分隔符分割,優(yōu)先在語義邊界處分割(包括中文句號(hào)、問號(hào)、感嘆號(hào)等)
String[] sentences = text.split("(?<=。)|(?<=!)|(?<=!)|(?<=?)|(?<=\\?)|(?<=\\n\\n)");
StringBuilder currentChunk = new StringBuilder();
for (String sentence : sentences) {
// 跳過空句子
if (sentence.trim().isEmpty()) {
continue;
}
// 如果當(dāng)前塊加上新句子不超過最大大小,就添加到當(dāng)前塊
if (currentChunk.length() + sentence.length() <= maxChunkSize) {
if (currentChunk.length() > 0) {
currentChunk.append(sentence);
} else {
currentChunk.append(sentence);
}
} else {
// 如果當(dāng)前塊為空,但是單個(gè)句子太長,需要強(qiáng)制分割
if (currentChunk.length() == 0) {
List<String> subChunks = forceSplit(sentence, maxChunkSize);
for (int i = 0; i < subChunks.size(); i++) {
String subChunk = subChunks.get(i);
// 如果不是最后一個(gè)子塊,添加到當(dāng)前塊并保存
if (i < subChunks.size() - 1) {
chunks.add(subChunk);
} else {
currentChunk.append(subChunk);
}
}
} else {
// 保存當(dāng)前塊
chunks.add(currentChunk.toString());
// 開始新塊,包含重疊部分
currentChunk = new StringBuilder();
// 添加重疊部分,如果句子長度大于重疊大小,則只取末尾部分
if (sentence.length() > overlapSize) {
String overlap = sentence.substring(Math.max(0, sentence.length() - overlapSize));
currentChunk.append(overlap);
currentChunk.append(sentence);
} else {
currentChunk.append(sentence);
}
}
}
}
// 添加最后一個(gè)塊
if (currentChunk.length() > 0) {
chunks.add(currentChunk.toString());
}
return chunks;
}
/**
* 強(qiáng)制將長文本分割成指定大小的塊
*
* @param text 輸入文本
* @param maxSize 最大塊大小
* @return 分割后的文本塊列表
*/
private List<String> forceSplit(String text, int maxSize) {
List<String> chunks = new ArrayList<>();
int start = 0;
while (start < text.length()) {
int end = Math.min(start + maxSize, text.length());
String chunk = text.substring(start, end);
chunks.add(chunk);
start = end;
}
return chunks;
}
/**
* 將多個(gè)文檔分別分割成塊
*
* @param documents 輸入文檔列表
* @return 分割后的文檔塊列表
*/
public List<Document> chunkDocuments(List<Document> documents) {
List<Document> allChunks = new ArrayList<>();
for (Document document : documents) {
allChunks.addAll(chunkDocument(document));
}
return allChunks;
}
}2.3 核心實(shí)現(xiàn)
配置Agent實(shí)現(xiàn)代碼評(píng)審
Bean定義與依賴注入
- CommandLineRunner: Spring啟動(dòng)后自動(dòng)執(zhí)行的接口
- ChatClient.Builder: 用于構(gòu)建聊天客戶端
- @Value(“${agent.skills.dirs:Unknown}”): 注入配置屬性,獲取技能目錄資源列表
ChatClient配置鏈
- 系統(tǒng)提示詞配置:
- 技能工具配置:
- SkillsTool.builder().addSkillsResources(agentSkillsDirs).build(): 動(dòng)態(tài)加載預(yù)定義的技能資源
- FileSystemTools.builder().build(): 提供文件系統(tǒng)訪問能力
- ShellTools.builder().build(): 提供命令行執(zhí)行能力
- Advisor配置:
- ToolCallAdvisor.builder().build(): 處理工具調(diào)用邏輯
- MyLoggingAdvisor.builder().showAvailableTools(false).showSystemMessage(false).build(): 自定義日志記錄,隱藏工具和系統(tǒng)消息詳情
代碼評(píng)審執(zhí)行流程
請(qǐng)求執(zhí)行
- prompt(): 構(gòu)建提示詞
- .call(): 發(fā)起AI請(qǐng)求
- .content(): 獲取返回結(jié)果
@Bean
CommandLineRunner commandLineRunner(ChatClient.Builder chatClientBuilder,
@Value("${agent.skills.dirs:Unknown}") List<Resource> agentSkillsDirs) throws IOException {
return args -> {
ChatClient chatClient = chatClientBuilder // @formatter:off
.defaultSystem("始終運(yùn)用現(xiàn)有技能協(xié)助用戶滿足其要求.")
// Skills tool
.defaultToolCallbacks(SkillsTool.builder().addSkillsResources(agentSkillsDirs).build())
// 支持讀取系統(tǒng)文件內(nèi)容,用于讀取我們需要評(píng)審的代碼
.defaultTools(FileSystemTools.builder().build())
// 支持執(zhí)行腳本,如果skills中存在script,那么這些腳本的執(zhí)行,靠的就是它
.defaultTools(ShellTools.builder().build())
.defaultAdvisors(
// Tool Calling advisor
ToolCallAdvisor.builder().build(),
// Custom logging advisor
MyLoggingAdvisor.builder()
.showAvailableTools(false)
.showSystemMessage(false)
.build())
.build();
// @formatter:on
var answer = chatClient
// 下面具體的代碼位置,請(qǐng)根據(jù)實(shí)際的位置進(jìn)行替換
.prompt("""
按照最佳實(shí)際的方式,評(píng)審下面的代碼實(shí)現(xiàn):
D:\\Workspace\\hui\\project\\spring-ai-demo\\v2\\T01-agentic-skills-simple-design\\src\\main\\java\\com\\git\\hui\\springai\\app\\demo\\DocumentChunker.java
""")
.call()
.content();
System.out.println("The Answer: " + answer);
};
}2.4 執(zhí)行演示
接下來我們啟動(dòng)項(xiàng)目,驗(yàn)證一下效果如何(在啟動(dòng)命令行參數(shù)中,配置上大模型的api-key,當(dāng)然也可以直接在yml配置文件中進(jìn)行配置)
@Slf4j
@SpringBootApplication
public class T01Application {
public static void main(String[] args) {
SpringApplication.run(T01Application.class, args);
}
}

從上面的截圖也可以看出,系統(tǒng)與大模型之間進(jìn)行了三輪對(duì)話,
- 第一輪: 用戶的原始訴求 -> 大模型
- 大模型識(shí)別到需要進(jìn)行代碼評(píng)審,給系統(tǒng)響應(yīng)
{"command":"code-reviewer"}
- 第二輪:系統(tǒng)接收到響應(yīng)之后,讀取skills的約束進(jìn)行響應(yīng)
- 大模型返回需要讀取需要評(píng)審的代碼內(nèi)容,對(duì)應(yīng)的響應(yīng)為
{"filePath":"D:\\Workspace\\hui\\project\\spring-ai-demo\\v2\\T01-agentic-skills-simple-design\\src\\main\\java\\com\\git\\hui\\springai\\app\\demo\\DocumentChunker.java"}
- 第三輪:系統(tǒng)讀取代碼內(nèi)容,發(fā)送給大模型
- 大模型返回評(píng)審結(jié)果
下面是完整的返回文本,有興趣的小伙伴可以看看
基于對(duì)代碼的詳細(xì)分析,我為您提供以下代碼評(píng)審報(bào)告:
## ?? 代碼評(píng)審報(bào)告 - DocumentChunker.java
### ? **優(yōu)點(diǎn)**
1. **功能完整性強(qiáng)**:代碼實(shí)現(xiàn)了完整的文檔分塊功能,包括:
- 單文檔分塊
- 批量文檔分塊
- 智能語義邊界分割
- 重疊機(jī)制
- 長文本強(qiáng)制分割
2. **良好的文檔注釋**:類和方法都有清晰的中文注釋,說明了功能用途
3. **合理的默認(rèn)值**:提供了合理的默認(rèn)參數(shù)(maxChunkSize=500, overlapSize=50)
4. **元數(shù)據(jù)保留**:正確保留了原始文檔的元數(shù)據(jù),并添加了塊相關(guān)的元數(shù)據(jù)
### ?? **需要改進(jìn)的問題**
#### 1. **線程安全問題**
// 問題:靜態(tài)實(shí)例不是線程安全的
public static DocumentChunker DEFAULT_CHUNKER = new DocumentChunker();
**建議**:使用 `volatile` 或 `AtomicReference`,或者移除靜態(tài)實(shí)例讓用戶自己管理。
#### 2. **參數(shù)驗(yàn)證不足**
// 問題:構(gòu)造函數(shù)沒有參數(shù)驗(yàn)證
public DocumentChunker(int maxChunkSize, int overlapSize) {
this.maxChunkSize = maxChunkSize;
this.overlapSize = overlapSize;
}
**建議**:添加參數(shù)驗(yàn)證,確保 `maxChunkSize > 0` 且 `overlapSize < maxChunkSize`。
#### 3. **正則表達(dá)式復(fù)雜度**
// 問題:正則表達(dá)式過于復(fù)雜,難以維護(hù)
String[] sentences = text.split("(?<={。})|(?<=!)|(?<=!)|(?<=?)|(?<=\\?)|(?<=\\n\\n)");
**建議**:提取為常量或使用更清晰的方式處理多語言分隔符。
#### 4. **性能優(yōu)化空間**
// 問題:StringBuilder操作可以優(yōu)化
if (currentChunk.length() > 0) {
currentChunk.append(sentence);
} else {
currentChunk.append(sentence);
}
**建議**:簡化為 `currentChunk.append(sentence);`
#### 5. **異常處理缺失**
代碼中沒有處理可能的異常情況,如:
- 內(nèi)存不足
- 文檔過大導(dǎo)致性能問題
### ?? **具體改進(jìn)建議**
#### 1. **添加參數(shù)驗(yàn)證**
public DocumentChunker(int maxChunkSize, int overlapSize) {
if (maxChunkSize <= 0) {
throw new IllegalArgumentException("maxChunkSize must be positive");
}
if (overlapSize >= maxChunkSize) {
throw new IllegalArgumentException("overlapSize must be less than maxChunkSize");
}
this.maxChunkSize = maxChunkSize;
this.overlapSize = overlapSize;
}
#### 2. **線程安全改進(jìn)**
public static final DocumentChunker DEFAULT_CHUNKER = new DocumentChunker();
// 或者移除靜態(tài)實(shí)例,讓用戶自己管理
#### 3. **提取常量**
private static final String SENTENCE_SEPARATOR_PATTERN =
"(?<={。})|(?<=!)|(?<=!)|(?<=?)|(?<=\\?)|(?<=\\n\\n)";
#### 4. **添加Builder模式**
考慮使用Builder模式來更靈活地配置參數(shù)。
#### 5. **性能優(yōu)化**
// 優(yōu)化StringBuilder操作
private List<String> splitText(String text) {
List<String> chunks = new ArrayList<>();
String[] sentences = text.split(SENTENCE_SEPARATOR_PATTERN);
StringBuilder currentChunk = new StringBuilder();
for (String sentence : sentences) {
if (sentence.trim().isEmpty()) continue;
if (currentChunk.length() + sentence.length() <= maxChunkSize) {
currentChunk.append(sentence);
} else {
// 處理塊分割邏輯
}
}
// ...
}
### ?? **總體評(píng)價(jià)**
這是一個(gè)功能完整、設(shè)計(jì)合理的文檔分塊工具類,主要問題集中在線程安全、參數(shù)驗(yàn)證和代碼可維護(hù)性方面。通過上述改進(jìn),可以顯著提升代碼的健壯性和可維護(hù)性。
**建議優(yōu)先級(jí)**:
1. 高優(yōu)先級(jí):參數(shù)驗(yàn)證、線程安全
2. 中優(yōu)先級(jí):性能優(yōu)化、代碼簡化
3. 低優(yōu)先級(jí):Builder模式、異常處理三、小結(jié)
SpringAI的Agent開發(fā)范式配合Skills機(jī)制,非常簡單就實(shí)現(xiàn)了AI應(yīng)用的工程化。整個(gè)過程實(shí)現(xiàn)下來,門檻還是比較低的。 不得不高喊一聲:Spring????
實(shí)現(xiàn)方式雖然簡單,但是這個(gè)背后的設(shè)計(jì)哲學(xué)、開發(fā)思維的轉(zhuǎn)變,還是很值得我們學(xué)習(xí)參考的——在AI時(shí)代,如何將我們現(xiàn)有的技能(如可復(fù)用、模塊化思設(shè)計(jì)等)轉(zhuǎn)變到大模型應(yīng)用開發(fā),這可能是我們每一個(gè)舊時(shí)代程序員最大的財(cái)富。
那么這一套是怎么實(shí)現(xiàn)的呢?

Spring AI采用基于工具的集成方法,通過實(shí)現(xiàn)各種工具,使任何LLM都能回調(diào)執(zhí)行,Skills的運(yùn)行過程,通常是下面三步:
- 發(fā)現(xiàn)(啟動(dòng)階段)
- 通過
SKILL.md文件中的元數(shù)據(jù),快速實(shí)現(xiàn)技能的安裝注冊(cè)
- 語義匹配(對(duì)話過程中)
- 當(dāng)用戶發(fā)出請(qǐng)求時(shí),LLM 會(huì)檢查工具定義中嵌入的技能描述。如果 LLM 判斷用戶請(qǐng)求在語義上與某個(gè)技能的描述匹配,則會(huì)調(diào)用該技能工具,并將技能名稱作為參數(shù)傳遞給它。
- 執(zhí)行(技能調(diào)用時(shí))
- 當(dāng)調(diào)用技能工具時(shí),SkillsTool會(huì)從磁盤加載完整的SKILL.md內(nèi)容,并將其與技能的基礎(chǔ)目錄路徑一起返回給大型語言模型(LLM)。然后,LLM會(huì)按照技能內(nèi)容中的指令執(zhí)行。如果技能引用了其他文件或輔助腳本,LLM會(huì)使用
FileSystemTools的Read函數(shù)或ShellTools的Bash函數(shù)來按需訪問它們
項(xiàng)目源碼:
實(shí)戰(zhàn)
- 大模型應(yīng)用開發(fā)實(shí)戰(zhàn):兩百行實(shí)現(xiàn)一個(gè)自然語言地址提取智能體
- 大模型應(yīng)用開發(fā)實(shí)戰(zhàn):基于SpringAI與大模型的零配置發(fā)票智能提取架構(gòu)
- 實(shí)戰(zhàn) | 零基礎(chǔ)搭建知識(shí)庫問答機(jī)器人:基于SpringAI+RAG的完整實(shí)現(xiàn)
參考:
到此這篇關(guān)于SpringAI Agent開發(fā)秘籍如何讓javaer也可以用上Agent Skills的文章就介紹到這了,更多相關(guān)SpringAI Agent Skills使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- AgentScope?Java?入門到進(jìn)階指南
- Java智能體AI?Agent開發(fā)中常見誤區(qū)與避坑指南
- SpringBoot基于Java Agent實(shí)現(xiàn)項(xiàng)目監(jiān)控
- SpringBoot?UserAgentUtils獲取用戶瀏覽器的用法
- 基于SpringAI+Qdrant+Ollama本地模型和向量數(shù)據(jù)庫開發(fā)問答和RAG檢索(完整代碼)
- springAI結(jié)合ollama簡單實(shí)現(xiàn)小結(jié)
- Deepseek整合SpringAI詳細(xì)流程
- 基于SpringAI+DeepSeek實(shí)現(xiàn)流式對(duì)話功能
相關(guān)文章
springboot yml配置文件定義list集合、數(shù)組和map以及使用中的錯(cuò)誤
這篇文章主要介紹了springboot yml配置文件定義list集合、數(shù)組和map以及使用中遇到的錯(cuò)誤問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
springboot?vue接口測試前后端樹節(jié)點(diǎn)編輯刪除功能
這篇文章主要為大家介紹了springboot?vue接口測試前后端樹節(jié)點(diǎn)編輯刪除功能,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
struts2數(shù)據(jù)處理_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Struts2框架框架使用OGNL語言和值棧技術(shù)實(shí)現(xiàn)數(shù)據(jù)的流轉(zhuǎn)處理。下面通過本文給大家分享struts2數(shù)據(jù)處理的相關(guān)知識(shí),感興趣的朋友參考下吧2017-09-09
Java中一個(gè)線程執(zhí)行死循環(huán)有什么后果
這篇文章主要為大家詳細(xì)介紹了Java中一個(gè)線程執(zhí)行死循環(huán)有什么后果,當(dāng)一個(gè)線程在執(zhí)行死循環(huán)時(shí)會(huì)影響另外一個(gè)線程嗎,下面為大家揭曉2016-05-05
詳解springboot使用異步注解@Async獲取執(zhí)行結(jié)果的坑
本文主要介紹了springboot使用異步注解@Async獲取執(zhí)行結(jié)果的坑,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Java實(shí)現(xiàn)md5和base64加密解密的示例代碼
這篇文章主要介紹了Java實(shí)現(xiàn)md5和base64加密解密的示例代碼,幫助大家更好的利用Java加密解密文件,感興趣的朋友可以了解下2020-09-09

