阿里智能AI框架Playground,即學即用

Spring AI Alibaba Playground 是 Spring AI Alibaba 社區以 Spring AI Alibaba 和 Spring AI 為框架搭建的 AI 應用。包含完善的前端 UI + 后端實現,具備對話,圖片生成,工具調用,RAG,MCP 等眾多 AI 相關功能。在 playground 的基礎之上,您可以快速復刻出一個屬于自己的 AI 應用。其中工具調用,MCP 集成,聊天模型切換等功能亦可為您搭建自己的 AI 應用提供參考。

Playground 代碼地址:https://github.com/springaialibaba/spring-ai-alibaba-examples/tree/main/spring-ai-alibaba-playground

項目首頁預覽:

image-20250607164742879

本篇文章中,內容較多。涉及運行,項目介紹,配置介紹等。分為三個部分介紹 Spring AI Alibaba Playground,您可以根據自己的需要跳轉到不同章節瀏覽。

目錄
本地運行
配置介紹
項目介紹
1. 本地運行
本章節中,將主要介紹如何在本地啟動 Playground 項目。

1. 1 下載源碼
Playground 代碼地址:https://github.com/springaialibaba/spring-ai-alibaba-examples/tree/main/spring-ai-alibaba-playground

playground 項目位于 spring-ai-alibaba-example 倉庫下,被設計為一個獨立的項目,不依賴于 spring-ai-alibaba-example pom 管理。這意味著您需要使用 IDEA 單獨打開 playground 項目目錄。而不是在 example 根目錄下直接啟動。

PS: 如果直接啟動需要配置 IDEA 的運行工作目錄,請參考 README 描述:https://github.com/springaialibaba/spring-ai-alibaba-examples/blob/main/spring-ai-alibaba-playground/README.md

1.2 配置變更
1.2.1 MCP 配置變更
因為 playground 項目引入了 mcp stdio 的方式來演示 Spring AI 如何接入 MCP 服務。因此當您的啟動環境為 windows 時,需要安裝并配置啟動 MCP Server 需要的環境。

以下面的 MCP Server json 配置文件為例:

{
"mcpServers": {
"github": {
"command": "npx",
"args": [
"/c",
"npx",
"-y",
"@modelcontextprotocol/server-github"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "your_github_token"
}
}
}
}

您需要安裝 NPX 工具,如果在 Windows 系統啟動,需要變更 command 為 npx.cmd。否則會啟動失敗。

1.2.2 數據庫文件變更
Playground 使用 SQLite 作為 Chat Memory 的存儲數據庫。在項目啟動時,會自動在 src/main/resources 目錄下創建 saa.db 的數據庫文件,如果啟動時沒有自動創建,出現啟動失敗錯誤時。您需要手動創建此文件。

1.2.3 前端打包
playground 前端項目采用打包編譯到 jar 包中一起啟動方式運行,因此在啟動后端時,需要運行 mvn clean package。確保前端項目正確打包編譯,您可以在 target/classes/static 路徑下看到前端資源文件。

1.2.4 可觀測集成
Playground 項目中集成了 Spring AI 的可觀測功能,如果您不想觀察 AI 應用運行中的一些指標數據,您可以忽略此步驟。

PS: 因為 AI 大模型應用的觀測數據中包含用戶的輸入等信息,在生產部署時,請確保敏感信息選項關閉。

在 spring-ai-alibaba-example 倉庫 docker-compose 目錄準備了 AI 應用常用的工具 docker-compose 啟動文件,您可以參考啟動 zipkin。

可觀測實現參考:https://java2ai.com/blog/spring-ai-alibaba-observability-arms/?spm=5176.29160081.0.0.2856aa5cenvkmu

1.2.5 apiKey 配置
Playground 項目集成了 RAG,向量數據庫和 Function Call 等功能,因此在啟動之初您應該配置對應的 ak。

PS: playgrond 中所有的 key 都通過 env 的方式注入,如果配置了 env 之后,項目仍然獲取不到 ak,請重啟 IDEA

DashScope 大模型 ak:AI 應用使用
阿里云 IQS 信息檢索服務 ak:模塊化 RAG 示例,web search 使用
阿里云 Analytic 項目數據庫 ak:RAG 使用;
百度翻譯和百度地圖 ak:Function Call 調用使用;
Github 個人 secret:MCP Server 演示使用。
關于 AK 的獲取方式自行搜索,這里不在過多贅述。

1.3 啟動并訪問
如果上面的配置步驟全部完成,在 playground 項目啟動之后,在瀏覽器輸入 http://localhost:8080 您將會看到文章開始時的首頁頁面。

image-20250607171739807

PS: 如果體驗對應的 Function Call 或者 MCP 功能時,請確保配置了對應的服務 AK 且 AK 有效。

此項目僅作為演示使用,一些功能初具形狀,尚不完善。歡迎貢獻代碼并完善項目!🚀

2. 配置介紹
playground 項目作為一個較完善的 AI 應用項目,涉及較多的配置文件,在此章節中將一一說明。

2.1 resources 配置
resource 目錄配置文件如下:

resources
├── application-dev.yml
├── application-prod.yml
├── application.yml
├── banner.txt
├── db
├── logback-spring.xml
├── mcp-config.yml
├── mcp-libs
├── models.yaml
└── rag

db 為 saa.db 目錄,主要為 playground 的 chat memory 提供存儲支持;
mcp-libs:MCP Stdio 的服務 jar 目錄;
rag:RAG 功能的知識庫文檔目錄,在項目啟動時,將自動向量化文檔并存入向量數據庫;
mcp-config.yaml:palyground 項目增強的 mcp-server 配置;
application-*.yml:項目啟動配置。
2.1.1 MCP Config 增強
解決的問題:在 playground 中使用 MCP Stdio 的方式來集成和演示 MCP 功能,在涉及到本地服務時,例如以下配置:

{
"mcpServers": {
"weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"D:\\open_sources\\spring-ai-alibaba-examples\\spring-ai-alibaba-mcp-example\\spring-ai-alibaba-mcp-build-example\\mcp-stdio-server-example\\target\\mcp-stdio-server-example-1.0.0.jar"
],
"env": {}
}
}
}

在二進制文件配置時,必須要求使用絕對路徑配置且 json 配置較難理解。因此 playground 在配置做了增強,將 json 轉為了語義清晰的 yml 方式定義。細節請參考 MCP

2.2 pom.xml 配置
此章節部分將主要介紹核心依賴,其他依賴請參考:https://github.com/springaialibaba/spring-ai-alibaba-examples/blob/main/spring-ai-alibaba-playground/pom.xml

<dependencies>

? ? <!-- Chat Memory 功能實現時需要此依賴項 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

? ? <!-- playground 文本總結功能依賴 tika 對輸入的各類文本進行解析 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
<version>${spring-ai.version}</version>
</dependency>

? ? <!-- Spring AI MCP client 相關依賴-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>${spring-ai.version}</version>
</dependency>

? ? <dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-autoconfigure-mcp-client</artifactId>
<version>${spring-ai.version}</version>
</dependency>

? <!-- Spring AI OpenAI Starter -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
<version>${spring-ai.version}</version>
</dependency>

? ? <!-- Spring AI RAG markdown 文本讀入解析 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
<version>${spring-ai.version}</version>
</dependency>

? ? <!-- Spring AI 向量數據庫 Advisors -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
<version>${spring-ai.version}</version>
</dependency>

? ? <!-- Spring AI Alibaba DashScope starter -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>

? ? <!-- Spring AI Alibaba Memory 實現 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>

? ? <!-- Spring AI Alibaba analyticdb 向量數據庫集成 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-store-analyticdb</artifactId>
<version>${spring-ai-alibaba.version}</version>
</dependency>

? ? <!-- DB,為 ChatMemory 和 playground 提供存儲支持 -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>${sqlite-jdbc.version}</version>
</dependency>

? ? <dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
<version>${hibernate.version}</version>
</dependency>

? ? <!-- Playground 可觀測集成 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
<version>${micrometr.version}</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>

</dependencies>

<!-- Spring AI 和 Spring AI Alibaba 依賴管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>

3. 項目介紹
在 playground 中集成了較多功能,RAG,MCP,Function Call 等。此章節中將主要對 RAG Web Search,MCP 調用,Function Call 拆分介紹。以便能夠基于此 playground 搭建符合自己需求的 AI 應用。

3.1 RAG 實現
RAG 仍然是當今最流行的 AI 應用結合私有知識庫的方式。通過 RAG 能夠構建問答機器人,專業領域助手等。

在 playground 項目中,使用的向量數據庫是 analyticdb 和基于內存的 SimpleVectorStore。您可以替換為任意您想使用的向量數據庫。

3.1.1 向量數據庫初始化
初始化配置代碼位于:com/alibaba/cloud/ai/application/config/rag

@Bean
CommandLineRunner ingestTermOfServiceToVectorStore(VectorStoreDelegate vectorStoreDelegate) {
return args -> {
String type = System.getenv("VECTOR_STORE_TYPE");
VectorStoreInitializer initializer = new VectorStoreInitializer();
initializer.init(vectorStoreDelegate.getVectorStore(type));
};
}

通過 VECTOR_STORE_TYPE 的方式來選擇使用那種類型的向量數據庫,VectorStoreDelegate 代碼如下:其作用是通過 type 的值返回向量數據庫的實例 bean。

PS:在這里您可以替換為您所使用的向量數據庫來構建 RAG 功能。

public class VectorStoreDelegate {

? ? private VectorStore simpleVectorStore;

? ? private VectorStore analyticdbVectorStore;

? ? public VectorStoreDelegate(VectorStore simpleVectorStore, VectorStore analyticdbVectorStore) {
this.simpleVectorStore = simpleVectorStore;
this.analyticdbVectorStore = analyticdbVectorStore;
}

? ? public VectorStore getVectorStore(String vectorStoreType) {

? ? ? ?if (Objects.equals(vectorStoreType, "analyticdb") && analyticdbVectorStore != null) {
return analyticdbVectorStore;
}

? ? ? ?return simpleVectorStore;
}
}

3.1.2 RAG 文檔初始化
在 VectorStoreInitializer 中將 resources/rag 下的 md 文檔向量化并加載到向量數據庫中:

public void init(VectorStore vectorStore) throws Exception {
List<MarkdownDocumentReader> markdownDocumentReaderList = loadMarkdownDocuments();

? ? int size = 0;
if (markdownDocumentReaderList.isEmpty()) {
logger.warn("No markdown documents found in the directory.");
return;
}

? ? logger.debug("Start to load markdown documents into vector store......");
for (MarkdownDocumentReader markdownDocumentReader : markdownDocumentReaderList) {
List<Document> documents = new TokenTextSplitter(2000, 1024, 10, 10000, true).transform(markdownDocumentReader.get());
size += documents.size();

? ? ? ? // 拆分 documents 列表為最大 25 個元素的子列表
for (int i = 0; i < documents.size(); i += 25) {
int end = Math.min(i + 25, documents.size());
List<Document> subList = documents.subList(i, end);
vectorStore.add(subList);
}
}
logger.debug("Load markdown documents into vector store successfully. Load {} documents.", size);
}

3.1.3 構建 Service
在業務代碼中注入向量數據庫 bean,即可完成 RAG 功能的實現。

@Service
public class SAARAGService {

? ? private final ChatClient client;

? ? private final VectorStoreDelegate vectorStoreDelegate;

? ? private String vectorStoreType;

? ? public SAARAGService(
VectorStoreDelegate vectorStoreDelegate,
SimpleLoggerAdvisor simpleLoggerAdvisor,
MessageChatMemoryAdvisor messageChatMemoryAdvisor,
@Qualifier("dashscopeChatModel") ChatModel chatModel,
@Qualifier("systemPromptTemplate") PromptTemplate systemPromptTemplate
) {
this.vectorStoreType = System.getenv("VECTOR_STORE_TYPE");
this.vectorStoreDelegate = vectorStoreDelegate;
this.client = ChatClient.builder(chatModel)
.defaultSystem(
systemPromptTemplate.getTemplate()
).defaultAdvisors(
messageChatMemoryAdvisor,
simpleLoggerAdvisor
).build();
}

? ? public Flux<String> ragChat(String chatId, String prompt) {

? ? ? ?return client.prompt()
.user(prompt)
.advisors(memoryAdvisor -> memoryAdvisor
.param(ChatMemory.CONVERSATION_ID, chatId)
).advisors(
QuestionAnswerAdvisor
.builder(vectorStoreDelegate.getVectorStore(vectorStoreType))
.searchRequest(
SearchRequest.builder()
// TODO all documents retrieved from ADB are under 0.1
// ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .similarityThreshold(0.6d)
.topK(6)
.build()
)
.build()
).stream()
.content();
}

}

RAG 實現文章參考:https://java2ai.com/blog/spring-ai-alibaba-rag-ollama/?spm=5176.29160081.0.0.2856aa5cenvkmu

3.2 Web Search 功能實現
在 Qwen 模型中,可以通過 enable_search 開啟模型的聯網搜索能力。在 playground 通過 Module RAG 的方式來集成聯網搜索功能。

3.2.1 Module RAG 介紹

Spring AI 實現了模塊化 RAG 架構,架構的靈感來自于論文“模塊化 RAG:將 RAG 系統轉變為類似樂高的可重構框架”中詳述的模塊化概念。將 RAG 分為三步:

Pre-Retrieval

增強和轉換用戶輸入,使其更有效地執行檢索任務,解決格式不正確的查詢、query 語義不清晰、或不受支持的語言等。

QueryAugmenter 查詢增強:使用附加的上下文數據信息增強用戶 query,提供大模型回答問題時的必要上下文信息;
QueryTransformer 查詢改寫:因為用戶的輸入通常是片面的,關鍵信息較少,不便于大模型理解和回答問題。因此需要使用 prompt 調優手段或者大模型改寫用戶 query;
QueryExpander 查詢擴展:將用戶 query 擴展為多個語義不同的變體以獲得不同視角,有助于檢索額外的上下文信息并增加找到相關結果的機會。
Retrieval

負責查詢向量存儲等數據系統并檢索和用戶 query 相關性最高的 Document。

DocumentRetriever:檢索器,根據 QueryExpander 使用不同的數據源進行檢索,例如 搜索引擎、向量存儲、數據庫或知識圖等;
DocumentJoiner:將從多個 query 和從多個數據源檢索到的 Document 合并為一個 Document 集合;
Post-Retrieval

負責處理檢索到的 Document 以獲得最佳的輸出結果,解決模型中的中間丟失和上下文長度限制等。

PS:Spring AI 在 1.0.0 中棄用了 DocumentRanker。您可以實現 DocumentPostProcessor 接口來實現此功能。Playground 待補充。

生成

生成用戶 Query 對應的大模型輸出。

3.2.2 數據來源
聯網搜索,顧名思義。就是將網絡上的數據通過實時搜索的方式獲取到并交給大模型來獲得最新得消息咨詢。playground 項目中使用了阿里云的 IQS,信息檢索服務作為聯網搜索的數據源。您可以使用搜索引擎服務替換 IQS。

IIQS 搜索實現如下:其本質為請求服務接口或調用 SDK。

public GenericSearchResult search(String query) {

? ? // String encodeQ = URLEncoder.encode(query, StandardCharsets.UTF_8);
ResponseEntity<GenericSearchResult> resultResponseEntity = run(query);

? ? return genericSearchResult(resultResponseEntity);
}

private ResponseEntity<GenericSearchResult> run(String query) {

? ? return this.restClient.get()
.uri(
"/search/genericSearch?query={query}&timeRange={timeRange}",
query,
TIME_RANGE
).retrieve()
.toEntity(GenericSearchResult.class);
}

}

3.2.3 數據加工
在這一步中,將搜索引擎獲取到的數據進行清洗并轉為 Spring AI 的 Document 文檔。

public List<Document> getData(GenericSearchResult respData) throws URISyntaxException {

? ? List<Document> documents = new ArrayList<>();

? ? Map<String, Object> metadata = getQueryMetadata(respData);

? ? for (ScorePageItem pageItem : respData.getPageItems()) {

? ? ? Map<String, Object> pageItemMetadata = getPageItemMetadata(pageItem);
Double score = getScore(pageItem);
String text = getText(pageItem);

? ? ? if (Objects.equals("", text)) {

? ? ? ? Media media = getMedia(pageItem);
Document document = new Document.Builder()
.metadata(metadata)
.metadata(pageItemMetadata)
.media(media)
.score(score)
.build();

? ? ? ? documents.add(document);
break;
}

? ? ? Document document = new Document.Builder()
.metadata(metadata)
.metadata(pageItemMetadata)
.text(text)
.score(score)
.build();

? ? ? documents.add(document);
}

? ? return documents;
}

? private Double getScore(ScorePageItem pageItem) {

? ? return pageItem.getScore();
}

? // .... 省略數據清洗代碼

? // 限制聯網搜索的文檔數,提高聯網搜索響應速度
public List<Document> limitResults(List<Document> documents, int minResults) {

? ? int limit = Math.min(documents.size(), minResults);

? ? return documents.subList(0, limit);
}

}

3.2.4 Module RAG 流程
接下來,便是使用 Module RAG API 處理用戶 Prompt。使其更符合大模型的輸入輸出,獲得更好的效果

具體代碼參考:https://github.com/springaialibaba/spring-ai-alibaba-examples/tree/main/spring-ai-alibaba-playground/src/main/java/com/alibaba/cloud/ai/application/rag

3.2.5 Web Search 服務類
在構造方法中注入相關 Bean;
在 ChatClient 中通過 RetrievalAugmentationAdvisor 引入 advisor 實現模塊化 RAG 的聯網搜索功能。
public SAAWebSearchService(
DataClean dataCleaner,
QueryExpander queryExpander,
IQSSearchEngine searchEngine,
QueryTransformer queryTransformer,
SimpleLoggerAdvisor simpleLoggerAdvisor,
@Qualifier("dashscopeChatModel") ChatModel chatModel,
@Qualifier("queryArgumentPromptTemplate") PromptTemplate queryArgumentPromptTemplate
) {

? ? this.queryTransformer = queryTransformer;
this.queryExpander = queryExpander;
this.queryArgumentPromptTemplate = queryArgumentPromptTemplate;

? ? // reasoning content for DeepSeek-r1 is integrated into the output
this.reasoningContentAdvisor = new ReasoningContentAdvisor(1);

? ? // Build chatClient
this.chatClient = ChatClient.builder(chatModel)
.defaultOptions(
DashScopeChatOptions.builder()
.withModel(DEFAULT_WEB_SEARCH_MODEL)
// stream 模式下是否開啟增量輸出
.withIncrementalOutput(true)
.build())
.build();

? ? // 日志
this.simpleLoggerAdvisor = simpleLoggerAdvisor;

? ? this.webSearchRetriever = WebSearchRetriever.builder()
.searchEngine(searchEngine)
.dataCleaner(dataCleaner)
.maxResults(2)
.build();
}

//Handle user input
public Flux<String> chat(String prompt) {

? ? return chatClient.prompt()
.advisors(
createRetrievalAugmentationAdvisor(),
reasoningContentAdvisor,
simpleLoggerAdvisor
).user(prompt)
.stream()
.content();
}

private RetrievalAugmentationAdvisor createRetrievalAugmentationAdvisor() {

? ? return RetrievalAugmentationAdvisor.builder()
.documentRetriever(webSearchRetriever)
.queryTransformers(queryTransformer)
.queryAugmenter(
new CustomContextQueryAugmenter(
queryArgumentPromptTemplate,
null,
true)
).queryExpander(queryExpander)
.documentJoiner(new ConcatenationDocumentJoiner())
.build();
}

Web Search 實現文章參考:https://java2ai.com/blog/spring-ai-alibaba-module-rag/?spm=5176.29160081.0.0.2856aa5cenvkmu&source=blog/

Spring AI RAG:https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html#_advisors

3.3 MCP ?集成
3.3.1 MCP Config 增強
為了解決 MCP Stdio json 配置文件難以理解和本地 MCP Server 二進制文件需要絕對路徑的問題。在 playground 中對 MCP Stdio 配置做了增強處理。

其主要步驟是將 McpStdioClientProperties 屬性配置重寫,以便在后續 MCP Client 初始化使用增強的 MCP 配置。

@Component
public class CustomMcpStdioTransportConfigurationBeanPostProcessor implements BeanPostProcessor {

? private static final Logger logger = LoggerFactory.getLogger(CustomMcpStdioTransportConfigurationBeanPostProcessor.class);

? private final ObjectMapper objectMapper;

? private final McpStdioClientProperties mcpStdioClientProperties;

? public CustomMcpStdioTransportConfigurationBeanPostProcessor(
ObjectMapper objectMapper,
McpStdioClientProperties mcpStdioClientProperties
) {
this.objectMapper = objectMapper;
this.mcpStdioClientProperties = mcpStdioClientProperties;
}

? @NotNull
@Override
public Object postProcessAfterInitialization(@NotNull Object bean, @NotNull String beanName) throws BeansException {

? ? if (bean instanceof StdioTransportAutoConfiguration) {

? ? ? logger.debug("增強 McpStdioTransportConfiguration bean start: {}", beanName);

? ? ? McpServerConfig mcpServerConfig;
try {
mcpServerConfig = McpServerUtils.getMcpServerConfig();

? ? ? ? // Handle the jar relative path issue in the configuration file.
for (Map.Entry<String, McpStdioClientProperties.Parameters> entry : mcpServerConfig.getMcpServers()
.entrySet()) {

? ? ? ? ? if (entry.getValue() != null && entry.getValue().command().startsWith("java")) {

? ? ? ? ? ? McpStdioClientProperties.Parameters serverConfig = entry.getValue();
String oldMcpLibsPath = McpServerUtils.getLibsPath(serverConfig.args());
String rewriteMcpLibsAbsPath = getMcpLibsAbsPath(McpServerUtils.getLibsPath(serverConfig.args()));
if (rewriteMcpLibsAbsPath != null) {
serverConfig.args().remove(oldMcpLibsPath);
serverConfig.args().add(rewriteMcpLibsAbsPath);
}
}
}

? ? ? ? String msc = objectMapper.writeValueAsString(mcpServerConfig);
logger.debug("Registry McpServer config: {}", msc);

? ? ? ? // write mcp client
mcpStdioClientProperties.setServersConfiguration(new ByteArrayResource(msc.getBytes()));
((StdioTransportAutoConfiguration) bean).stdioTransports(this.mcpStdioClientProperties);
}
catch (IOException e) {
throw new SAAAppException(e.getMessage());
}

? ? ? logger.debug("增強 McpStdioTransportConfiguration bean end: {}", beanName);
}

? ? return bean;
}

}

在 MCPServerUtils 中讀取 mcp-config.yaml 配置并轉為 McpServerConfig。

public static McpServerConfig getMcpServerConfig() throws IOException {

? ? ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
InputStream resourceAsStream = ModelsUtils.class.getClassLoader().getResourceAsStream(MCP_CONFIG_FILE_PATH);

? ? McpServerConfig mcpServerConfig = mapper.readValue(resourceAsStream, McpServerConfig.class);
mcpServerConfig.getMcpServers().forEach((key, parameters) -> {
Map<String, String> env = parameters.env();
if (Objects.nonNull(env)) {
env.entrySet().stream()
.filter(entry -> entry.getValue() != null && !entry.getValue().isEmpty() &&
entry.getValue().startsWith("${") && entry.getValue().endsWith("}"))
.forEach(entry -> {
String envKey = entry.getValue().substring(2, entry.getValue().length() - 1);
String envValue = System.getenv(envKey);
// allow env is null.
if (envValue != null && !envValue.isEmpty()) {
env.put(entry.getKey(), envValue);
}
});
}
});


return mcpServerConfig;
}

3.3.2 MCP Server 工具回顯
為了便于展示 MCP Client 如何調用 MCP Server 的 tools 和展示 MCP Server 中有哪些 Tools,playground 中做了特殊處理。

自定義 MCP Server 存放 MCP Server 的 tools 信息用于瀏覽器顯示:

public class McpServer {

? private String id;

? private String name;

? private String desc;

? private Map<String, String> env;

? private List<Tools> toolList;
}

因為 Spring AI 的 SyncMcpToolCallback 中的 MCPClient 沒有對外暴露獲取 MCP Server 的相關屬性,只有 Tools 定義。playground 對 SyncMcpToolCallback 做了包裝處理:

public class SyncMcpToolCallbackWrapper {

? private final SyncMcpToolCallback callback;

? public SyncMcpToolCallbackWrapper(SyncMcpToolCallback callback) {
this.callback = callback;
}

? public McpSyncClient getMcpClient() {

? ? try {
Field field = SyncMcpToolCallback.class.getDeclaredField("mcpClient");
field.setAccessible(true);
return (McpSyncClient) field.get(callback);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

}

在 MCPServerUtils 中做了 MCP Server 容器的初始化操作:

public static void initMcpServerContainer(ToolCallbackProvider toolCallbackProvider) throws IOException {

? ? McpServerConfig mcpServerConfig = McpServerUtils.getMcpServerConfig();
Map<String, String> mcpServerDescMap = initMcpServerDescMap();

? ? mcpServerConfig.getMcpServers().forEach((key, parameters) -> {

? ? ? ? List<McpServer.Tools> toolsList = new ArrayList<>();
for (ToolCallback toolCallback : toolCallbackProvider.getToolCallbacks()) {

? ? ? ? ? ? // todo: 拿不到 mcp client, 先用包裝器拿吧
SyncMcpToolCallback mcpToolCallback = (SyncMcpToolCallback) toolCallback;
SyncMcpToolCallbackWrapper syncMcpToolCallbackWrapper = new SyncMcpToolCallbackWrapper(mcpToolCallback);
String currentMcpServerName = syncMcpToolCallbackWrapper.getMcpClient().getServerInfo().name();

? ? ? ? ? ? // 按照 mcp server name 聚合 mcp server tools
if (Objects.equals(key, currentMcpServerName)) {
McpServer.Tools tool = new McpServer.Tools();
tool.setDesc(toolCallback.getToolDefinition().description());
tool.setName(toolCallback.getToolDefinition().name());
tool.setParams(toolCallback.getToolDefinition().inputSchema());

? ? ? ? ? ? ? ? toolsList.add(tool);
}
}

? ? ? ? McpServerContainer.addServer(McpServer.builder()
.id(getId())
.name(key)
.env(parameters.env())
.desc(mcpServerDescMap.get(key))
.toolList(toolsList)
.build()
);
});

}

MCP Server 工具回顯效果如下:您可以在 resource 下的 mcp-config.yaml 中添加更多 MCP Server。

image-20250607190058038

3.3.3 MCP 工具調用
完成上面一系列的初始化操作之后,接下來便是編寫 MCP Service 類:為了能夠獲取 MCP Server Tools 的執行信息,這里使用了 Spring AI Tools 的 internalToolExecutionEnabled API。來收集大模型的工具入參和執行結果等,在前端做調用展示。

為了收集 MCP Tools 調用過程中的信息。Playground 項目編寫了 ToolCallResp 類來收集一些信息:

public class ToolCallResp {

? ? /**
* Tool 的執行狀態
*/
private ToolState status;

? ? /**
* Tool Name
*/
private String toolName;

? ? /**
* Tool 執行參數
*/
private String toolParameters;

? ? /**
* Tool 執行結果
*/
private String toolResult;

? ? /**
* 工具執行開始的時間戳
*/
private LocalDateTime toolStartTime;

? ? /**
* 工具執行完成的時間戳
*/
private LocalDateTime toolEndTime;

? ? /**
* 工具執行的錯誤信息
*/
private String errorMessage;

? ? /**
* 工具執行輸入
*/
private String toolInput;

? ? /**
* 工具執行耗時
*/
private Long toolCostTime;
/**
* Tool 記錄tool返回的中間結果
*/
private String toolResponse;
}

MCP Service 實現:

@Service
public class SAAMcpService {

? private final ChatClient chatClient;

? private final ObjectMapper objectMapper;

? private final ToolCallbackProvider tools;

? private final ToolCallingManager toolCallingManager;

? private final McpStdioClientProperties mcpStdioClientProperties;

? private static final Logger logger = LoggerFactory.getLogger(SAAMcpService.class);

? public SAAMcpService(
ObjectMapper objectMapper,
ToolCallbackProvider tools,
SimpleLoggerAdvisor simpleLoggerAdvisor,
ToolCallingManager toolCallingManager,
McpStdioClientProperties mcpStdioClientProperties,
@Qualifier("openAiChatModel") ChatModel chatModel
) throws IOException {

? ? this.objectMapper = objectMapper;
this.mcpStdioClientProperties = mcpStdioClientProperties;

? ? // Initialize chat client with non-blocking configuration
this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
simpleLoggerAdvisor
).defaultToolCallbacks(tools)
.build();
this.tools = tools;
this.toolCallingManager = toolCallingManager;

? ? McpServerUtils.initMcpServerContainer(tools);
}

? public ToolCallResp chat(String prompt) {

? ? // manual run tools flag
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(tools.getToolCallbacks())
.internalToolExecutionEnabled(false)
.build();

? ? ChatResponse response = chatClient.prompt(new Prompt(prompt, chatOptions))
.call().chatResponse();

? ? logger.debug("ChatResponse: {}", response);
assert response != null;
List<AssistantMessage.ToolCall> toolCalls = response.getResult().getOutput().getToolCalls();
logger.debug("ToolCalls: {}", toolCalls);
String responseByLLm = response.getResult().getOutput().getText();
logger.debug("Response by LLM: {}", responseByLLm);

? ? // execute tools with no chat memory messages.
var tcr = ToolCallResp.TCR();
if (!toolCalls.isEmpty()) {

? ? ? tcr = ToolCallResp.startExecute(
responseByLLm,
toolCalls.get(0).name(),
toolCalls.get(0).arguments()
);
tcr.setToolParameters(toolCalls.get(0).arguments());
logger.debug("Start ToolCallResp: {}", tcr);
ToolExecutionResult toolExecutionResult = null;

? ? ? try {
toolExecutionResult = toolCallingManager.executeToolCalls(new Prompt(prompt, chatOptions), response);

? ? ? ? tcr.setToolEndTime(LocalDateTime.now());
}
catch (Exception e) {

? ? ? ? tcr.setStatus(ToolCallResp.ToolState.FAILURE);
tcr.setErrorMessage(e.getMessage());
tcr.setToolEndTime(LocalDateTime.now());
tcr.setToolCostTime((long) (tcr.getToolEndTime().getNano() - tcr.getToolStartTime().getNano()));
logger.error("Error ToolCallResp: {}, msg: {}", tcr, e.getMessage());
// throw new RuntimeException("Tool execution failed, please check the logs for details.");
}

? ? ? String llmCallResponse = "";
if (Objects.nonNull(toolExecutionResult)) {
ChatResponse finalResponse = chatClient.prompt().messages(toolExecutionResult.conversationHistory())
.call().chatResponse();
if (finalResponse != null) {
llmCallResponse = finalResponse.getResult().getOutput().getText();
}

? ? ? ? StringBuilder sb = new StringBuilder();
toolExecutionResult.conversationHistory().stream()
.filter(message -> message instanceof ToolResponseMessage)
.forEach(message -> {
ToolResponseMessage toolResponseMessage = (ToolResponseMessage) message;
toolResponseMessage.getResponses().forEach(tooResponse -> {
sb.append(tooResponse.responseData());
});
});
tcr.setToolResponse(sb.toString());
}

? ? ? tcr.setStatus(ToolCallResp.ToolState.SUCCESS);
tcr.setToolResult(llmCallResponse);
tcr.setToolCostTime((long) (tcr.getToolEndTime().getNano() - tcr.getToolStartTime().getNano()));
logger.debug("End ToolCallResp: {}", tcr);
}
else {
logger.debug("ToolCalls is empty, no tool execution needed.");
tcr.setToolResult(responseByLLm);
}

? ? return tcr;
}

? public ToolCallResp run(String id, Map<String, String> envs, String prompt) throws IOException {

? ? Optional<McpServer> runMcpServer = McpServerContainer.getServerById(id);
if (runMcpServer.isEmpty()) {
logger.error("McpServer not found, id: {}", id);
return ToolCallResp.TCR();
}

? ? String runMcpServerName = runMcpServer.get().getName();
var mcpServerConfig = McpServerUtils.getMcpServerConfig();
McpStdioClientProperties.Parameters parameters = new McpStdioClientProperties.Parameters(
mcpServerConfig.getMcpServers().get(runMcpServerName).command(),
mcpServerConfig.getMcpServers().get(runMcpServerName).args(),
envs
);

? ? if (parameters.command().startsWith("java")) {
String oldMcpLibsPath = McpServerUtils.getLibsPath(parameters.args());
String rewriteMcpLibsAbsPath = getMcpLibsAbsPath(McpServerUtils.getLibsPath(parameters.args()));

? ? ? parameters.args().remove(oldMcpLibsPath);
parameters.args().add(rewriteMcpLibsAbsPath);
}

? ? String mcpServerConfigJSON = objectMapper.writeValueAsString(mcpServerConfig);
mcpStdioClientProperties.setServersConfiguration(new ByteArrayResource(mcpServerConfigJSON.getBytes()));

? ? return chat(prompt);
}

}

Function Call 參考:https://docs.spring.io/spring-ai/reference/api/tools.html

MCP Server 文章參考:https://java2ai.com/blog/spring-ai-alibaba-mcp/?spm=5176.29160081.0.0.2856aa5cenvkmu

3.4 Function Call 集成
playground 中實現了 Function Call 的功能,和 MCP 一樣,支持調用狀態顯示。工具瀏覽器回顯同理。

3.4.1 Function Tools 初始化
您可以通過使用 Spring AI Alibaba 的提供的 Tool Calling Starter 來引入工具,也可以像 Playground 一樣,通過 FunctionToolCallback 來自定義工具。

Playground Tools: https://github.com/springaialibaba/spring-ai-alibaba-examples/tree/main/spring-ai-alibaba-playground/src/main/java/com/alibaba/cloud/ai/application/tools

Tools 初始化代碼如下:

public List<ToolCallback> getTools() {

? ? return List.of(buildBaiduTranslateTools(), buildBaiduMapTools());
}

private ToolCallback buildBaiduTranslateTools() {

? ? return FunctionToolCallback
.builder(
"BaiduTranslateService",
new BaiduTranslateTools(ak, sk, restClientbuilder, responseErrorHandler)
).description("Baidu translation function for general text translation.")
.inputSchema(
"""
{
"type": "object",
"properties": {
"Request": {
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "Content that needs to be translated."
},
"from": {
"type": "string",
"description": "Source language that needs to be translated."
},
"to": {
"type": "string",
"description": "Target language to translate into."
}
},
"required": ["q", "from", "to"],
"description": "Request object to translate text to a target language."
},
"Response": {
"type": "object",
"properties": {
"translatedText": {
"type": "string",
"description": "The translated text."
}
},
"required": ["translatedText"],
"description": "Response object for the translation function, containing the translated text."
}
},
"required": ["Request", "Response"]
}
"""
).inputType(BaiduTranslateTools.BaiduTranslateToolRequest.class)
.toolMetadata(ToolMetadata.builder().returnDirect(false).build())
.build();
}

3.4.2 Function Tools 調用
完成了工具引入或定義之后。接下來,便可以在 service 中使用這些 Tools 來增強大模型的能力。工具調用代碼和 MCP Server Tools 類似。

public class SAAToolsService {

? private static final Logger logger = LoggerFactory.getLogger(SAAToolsService.class);

? private final ChatClient chatClient;

? private final ToolCallingManager toolCallingManager;

? private final ToolsInit toolsInit;

? public SAAToolsService(
ToolsInit toolsInit,
ToolCallingManager toolCallingManager,
SimpleLoggerAdvisor simpleLoggerAdvisor,
MessageChatMemoryAdvisor messageChatMemoryAdvisor,
@Qualifier("openAiChatModel") ChatModel chatModel
) {

? ? this.toolsInit = toolsInit;
this.toolCallingManager = toolCallingManager;

? ? this.chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
simpleLoggerAdvisor
// ? ? ? ? ? ?messageChatMemoryAdvisor
).build();
}

? public ToolCallResp chat(String prompt) {

? ? // manual run tools flag
ChatOptions chatOptions = ToolCallingChatOptions.builder()
.toolCallbacks(toolsInit.getTools())
.internalToolExecutionEnabled(false)
.build();
Prompt userPrompt = new Prompt(prompt, chatOptions);

? ? ChatResponse response = chatClient.prompt(userPrompt)
.call().chatResponse();

? ? logger.debug("ChatResponse: {}", response);
assert response != null;
List<AssistantMessage.ToolCall> toolCalls = response.getResult().getOutput().getToolCalls();
logger.debug("ToolCalls: {}", toolCalls);
String responseByLLm = response.getResult().getOutput().getText();
logger.debug("Response by LLM: {}", responseByLLm);

? ? // execute tools with no chat memory messages.
var tcr = ToolCallResp.TCR();
if (!toolCalls.isEmpty()) {

? ? ? tcr = ToolCallResp.startExecute(
responseByLLm,
toolCalls.get(0).name(),
toolCalls.get(0).arguments()
);
logger.debug("Start ToolCallResp: {}", tcr);
ToolExecutionResult toolExecutionResult = null;

? ? ? try {
toolExecutionResult = toolCallingManager.executeToolCalls(new Prompt(prompt, chatOptions), response);

? ? ? ? tcr.setToolEndTime(LocalDateTime.now());
}
catch (Exception e) {

? ? ? ? tcr.setStatus(ToolCallResp.ToolState.FAILURE);
tcr.setErrorMessage(e.getMessage());
tcr.setToolEndTime(LocalDateTime.now());
tcr.setToolCostTime((long) (tcr.getToolEndTime().getNano() - tcr.getToolStartTime().getNano()));
logger.error("Error ToolCallResp: {}, msg: {}", tcr, e.getMessage());
// throw new RuntimeException("Tool execution failed, please check the logs for details.");
}

? ? ? String llmCallResponse = "";
if (Objects.nonNull(toolExecutionResult)) {
// ? ? ? ?ToolResponseMessage toolResponseMessage = (ToolResponseMessage) toolExecutionResult.conversationHistory()
// ? ? ? ? ? ?.get(toolExecutionResult.conversationHistory().size() - 1);
// ? ? ? ?llmCallResponse = toolResponseMessage.getResponses().get(0).responseData();
ChatResponse finalResponse = chatClient.prompt().messages(toolExecutionResult.conversationHistory()).call().chatResponse();
llmCallResponse = finalResponse.getResult().getOutput().getText();
}

? ? ? tcr.setStatus(ToolCallResp.ToolState.SUCCESS);
tcr.setToolResult(llmCallResponse);
tcr.setToolCostTime((long) (tcr.getToolEndTime().getNano() - tcr.getToolStartTime().getNano()));
logger.debug("End ToolCallResp: {}", tcr);
}
else {
logger.debug("ToolCalls is empty, no tool execution needed.");
tcr.setToolResult(responseByLLm);
}

? ? return tcr;
}

}

Function Tools 集成文章參考:https://java2ai.com/blog/spring-ai-toolcalling/?spm=5176.29160081.0.0.2856aa5cenvkmu

3.5 前端頁面
Playground 前端頁面使用 React 實現,為上述功能提供了一套基本的前端界面,您可以參考以下關鍵信息做一些自定義修改.

3.5.1 數據持久化
部分業務場景需要將數據持久化以便于查詢歷史記錄,生產環境使用時推薦使用服務端存儲記錄,但 Playground 出于效果演示目的,目前默認將所有歷史數據保存到客戶端本地,相關實現參考如下代碼:

/**
* 處理消息發送的完整流程
* @param text - 消息文本
* @param sendRequest - 發送請求的函數
* @param createMessage - 創建消息對象的函數
*/
const processSendMessage = async <T extends BaseMessage>({
text,
sendRequest,
createMessage,
}: {
text: string;
sendRequest: (text: string, timestamp: number, message: T) => Promise<void>;
createMessage: (text: string, timestamp: number) => T;
}) => {
if (!text.trim() || !activeConversation) return;

? ? const userTimestamp = Date.now();
const userMessage = createMessage(text, userTimestamp);

? ? // 存儲用戶數據到客戶端本地,生產環境可省略該步驟由服務端存儲
updateActiveConversation({
...activeConversation,
messages: [
...activeConversation.messages,
userMessage,
] as T[],
});

? ? try {
await sendRequest(text, userTimestamp, userMessage);
} catch (error) {
console.error("error:", error);
}
};

3.5.2 消息文本樣式自定義渲染
對話氣泡組件支持富文本樣式渲染,并且具備較好的拓展性,您可以參考如下代碼對任意標簽做自定義樣式拓展:

/**
* 獲取自定義渲染配置
* @param style - css 樣式
*/
const getMarkdownRenderConfig = (styles: Record<string, string>) => {

? return {
div: ({ children }) => {
return <pre className={styles.codeBlock}>{children}</pre>
},
code: ({ children, className }) => {
return <code className={styles.codeInline}>{children}</code>
},
think: ({ children }) => {
return ?<div className={styles.thinkTag}>{children}</div>;
},
tool: ({ children }) => {
return <div className={styles.toolTag}>{children}</div>;
},
};
};

您也可以通過正則匹配等方式進行語法分析,將特定格式的字符串修改為由自定義標簽包裹的形式,再由上述方式設置其渲染樣式,實現可交互表單、圖表等復雜樣式。

4. 總結
Spring AI Alibaba 官方社區開發了一個包含完整 前端UI+后端實現 的智能體 Playground 示例。未來社區會持續更新維護。以此來演示 Spring AI 和 Spring AI Alibaba 的最新功能。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/91544.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/91544.shtml
英文地址,請注明出處:http://en.pswp.cn/web/91544.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

智能Agent場景實戰指南 Day 28:Agent成本控制與商業模式

【智能Agent場景實戰指南 Day 28】Agent成本控制與商業模式 文章標簽 AI Agent, 成本優化, 商業模式, LLM應用, 企業級AI 文章簡述 本文是"智能Agent場景實戰指南"系列的第28天&#xff0c;聚焦智能Agent的成本控制與商業模式設計這一關鍵課題。文章首先分析了Ag…

sqli-labs:Less-8關卡詳細解析

1. 思路&#x1f680; 本關的SQL語句為&#xff1a; $sql"SELECT * FROM users WHERE id$id LIMIT 0,1";注入類型&#xff1a;字符串型&#xff08;單引號包裹&#xff09;提示&#xff1a;參數id需以閉合 同樣無法像常規一樣回顯&#xff0c;php輸出語句的代碼如下&…

LeetCode 1782.統計點對的數目

給你一個無向圖&#xff0c;無向圖由整數 n &#xff0c;表示圖中節點的數目&#xff0c;和 edges 組成&#xff0c;其中 edges[i] [ui, vi] 表示 ui 和 vi 之間有一條無向邊。同時給你一個代表查詢的整數數組 queries 。 第 j 個查詢的答案是滿足如下條件的點對 (a, b) 的數…

U-Mail郵件系統-全面適配信創環境的國產郵件系統

在當今數字化時代&#xff0c;郵件系統作為企業、政府機構以及各類組織日常辦公不可或缺的溝通工具&#xff0c;其安全性、穩定性以及自主可控性的重要性日益凸顯。隨著信創產業的蓬勃發展&#xff0c;國產郵件系統應運而生&#xff0c;成為保障信息安全、推動數字化轉型的關鍵…

【LeetCode 熱題 100】394. 字符串解碼

Problem: 394. 字符串解碼 給定一個經過編碼的字符串&#xff0c;返回它解碼后的字符串。 編碼規則為: k[encoded_string]&#xff0c;表示其中方括號內部的 encoded_string 正好重復 k 次。注意 k 保證為正整數。 你可以認為輸入字符串總是有效的&#xff1b;輸入字符串中沒有…

Activity之間互相發送數據

activity_send_data_req.xml<?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_pare…

設計模式:訪問者模式 Visitor

目錄前言問題解決方案結構代碼前言 訪問者是一種行為設計模式&#xff0c;它能將算法與其所作用的對象隔離開來。 問題 假如你的團隊開發了一款能夠使用巨型圖像中地理信息的應用程序。 圖像中的每個節點既能代表復雜實體&#xff08;例如一座城市&#xff09;&#xff0c; 也…

OpenCV 學習探秘之四:從角點檢測,SIFT/SURF/ORB特征提取,目標檢測與識別,Haar級聯分類人臉檢測,再到機器學習等接口的全面實戰應用與解析

書接上回&#xff0c;前面介紹了一些基本應用&#xff0c;本篇則著重介紹一些比較復雜的應用。 附&#xff1a;本文所用例子中使用的Opencv庫OpenCV4.5.4版本編譯好的庫 五、特征提取與描述 5.1 角點檢測&#xff1a;Harris 角點和 Shi-Tomasi 角點 5.1.1 Harris 角點檢測&a…

《秋招在即!Redis數據類型面試題解析》

博客主頁&#xff1a;天天困啊系列專欄&#xff1a;面試題關注博主&#xff0c;后期持續更新系列文章如果有錯誤感謝請大家批評指出&#xff0c;及時修改感謝大家點贊&#x1f44d;收藏?評論? Redis中常見的基礎數據結構總共五種&#xff1a;這五種類型分別為String&#xff…

政務網站內容檢測系統對錯敏信息有什么作用

政務網站內容檢測系統在錯敏信息管理中發揮著重要作用&#xff0c;能夠有效提升政府網站的信息安全性與合規性。以下從錯敏信息的作用及蟻巡政務信息巡查系統的功能特點兩方面進行說明。一、政務網站內容檢測系統對錯敏信息的作用1、實時監測與識別內容檢測系統通過智能化技術對…

Tower of Hanoi 漢諾塔

題目描述The Tower of Hanoi game consists of three stacks (left, middle and right) and n round disks of different sizes. Initially, the left stack has all the disks, in increasing order of size from top to bottom. The goal is to move all the disks to the r…

在 Docker 中啟動 Nginx 并掛載配置文件到宿主機目錄

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 在 Docker 中啟動 Nginx 并掛載配置文件到宿主機目錄前言一、創建宿主機目錄存放 Nginx 配置1.1 在宿主機&#xff08;如 Windows 或 Linux&#xff09;上創建目錄&#xff0…

認識ansible(入門)

什么是ansible&#xff1f;Ansible是一款自動化運維工具&#xff0c;基于Python開發&#xff0c;集合了眾多運維工具&#xff08;puppet、cfengine、chef、func、fabric&#xff09;的優點&#xff0c;實現了批量系統配置、批量程序部署、批量運行命令等功能。Ansible是基于模塊…

Apache Ignite 關于 **Executor Service(執行器服務)** 的介紹

這段內容是 Apache Ignite 關于 Executor Service&#xff08;執行器服務&#xff09; 的介紹。我們可以把它理解為&#xff1a;一個分布式的“線程池”&#xff0c;可以把任務分發到集群中的多個節點上去執行。 下面我用通俗易懂的方式幫你徹底理解這個概念。&#x1f310; 什…

應用Builder模式在C++中進行復雜對象構建

引言 在軟件開發中&#xff0c;構建復雜對象時常常會遇到構造函數或setter方法過于復雜的情況。Builder模式作為一種創建型設計模式&#xff0c;能夠有效地解決這一問題。Guoyao 創建的勇勇(YongYong)角色&#xff0c;通過Builder模式實現了對復雜對象的構建過程與表示的分離&a…

gradio作為原型工具

存在的問題&#xff0c;頁面的展示和value不是同一個值的問題&#xff0c;比如說選中了北京&#xff0c;但實際上后端想要的是110000地區碼。 在實際開發中&#xff0c;前端展示給用戶的是可讀的地區名稱&#xff08;如“北京”&#xff09;&#xff0c;而背后處理時通常需要的…

計算聲子譜

準備的還是vasp的必備文件&#xff1a;POSCAR POTCAR KPOINTS&#xff0c;剩下需要的INCAR、band文件下面代碼可以生成&#xff1a;#!/bin/bash if [ ! -f band.conf ];then cat >>band.conf <<EOF ATOM_NAME Ti Al B DIM 1 1 1 BAND 0.0 0.0 0.0 0.5 -0.5 0.5…

深度學習 目標檢測常見指標和yolov1分析

目錄 一、常見指標 1、IoU 2、Confidence置信度 3、精準度和召回率 4、mAP 5、NMS方法 6、檢測速度 前傳耗時 FPS 7、FLOPs 二、YOLOv1 檢測流程 1、圖像網格劃分 2、類別預測 3、輸出張量 損失函數 優點 缺點 如題&#xff0c;這篇介紹一下目標檢測中常見的…

31. 偽類和偽元素區別

總結 選擇對象不同內容說明偽類作用對象元素的狀態或位置偽元素作用對象元素的一部分內容或虛擬內容是否新增節點均不新增節點常用符號:&#xff08;偽類&#xff09;、::&#xff08;偽元素&#xff09;推薦場景偽類用于交互與狀態控制&#xff1b;偽元素用于樣式修飾與內容插…

ChatGPT、Playground手動模擬Agent摘要緩沖混合記憶功能

01. 摘要緩沖混合記憶 摘要緩沖混合記憶中&#xff0c;所需的模塊有&#xff1a; chat_message_history&#xff1a;存儲歷史消息列表。moving_summary_buffer&#xff1a;移除消息的匯總字符串。summary_llm&#xff1a;生成摘要的 LLM&#xff0c;接收 summary&#xff08;…