mirror of
https://gitee.com/yudaocode/yudao-boot-mini.git
synced 2025-12-26 07:06:22 +08:00
feat:【ai 大模型】增加 MCP Server Boot Starter 示例
This commit is contained in:
parent
4afa67f34e
commit
5b31f27bd5
@ -187,6 +187,12 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
||||
<version>${spring-ai.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TinyFlow:AI 工作流 -->
|
||||
<dependency>
|
||||
<groupId>dev.tinyflow</groupId>
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
package cn.iocoder.yudao.module.ai.dal.dataobject.model;
|
||||
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.module.ai.service.model.tool.DirectoryListToolFunction;
|
||||
import cn.iocoder.yudao.module.ai.service.model.tool.WeatherQueryToolFunction;
|
||||
import cn.iocoder.yudao.module.ai.tool.function.DirectoryListToolFunction;
|
||||
import cn.iocoder.yudao.module.ai.tool.function.WeatherQueryToolFunction;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
||||
import cn.iocoder.yudao.module.ai.tool.method.PersonService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
||||
@ -25,8 +26,10 @@ import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.support.ToolCallbacks;
|
||||
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
|
||||
import org.springframework.ai.tokenizer.TokenCountEstimator;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties;
|
||||
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties;
|
||||
import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
|
||||
@ -36,6 +39,8 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 芋道 AI 自动配置
|
||||
*
|
||||
@ -271,4 +276,14 @@ public class AiAutoConfiguration {
|
||||
return new AiBoChaWebSearchClient(yudaoAiProperties.getWebSearch().getApiKey());
|
||||
}
|
||||
|
||||
// ========== MCP 相关 ==========
|
||||
|
||||
/**
|
||||
* 参考自 <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html">MCP Server Boot Starter</>
|
||||
*/
|
||||
@Bean
|
||||
public List<ToolCallback> toolCallbacks(PersonService personService) {
|
||||
return List.of(ToolCallbacks.from(personService));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
package cn.iocoder.yudao.module.ai.framework.security.config;
|
||||
|
||||
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
|
||||
|
||||
/**
|
||||
* AI 模块的 Security 配置
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false, value = "aiSecurityConfiguration")
|
||||
public class SecurityConfiguration {
|
||||
|
||||
@Resource
|
||||
private McpServerProperties serverProperties;
|
||||
|
||||
@Bean("aiAuthorizeRequestsCustomizer")
|
||||
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
|
||||
return new AuthorizeRequestsCustomizer() {
|
||||
|
||||
@Override
|
||||
public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
|
||||
// MCP Server
|
||||
registry.requestMatchers(serverProperties.getSseEndpoint()).permitAll();
|
||||
registry.requestMatchers(serverProperties.getSseMessageEndpoint()).permitAll();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 占位
|
||||
*/
|
||||
package cn.iocoder.yudao.module.ai.framework.security.core;
|
||||
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.ai.service.model.tool;
|
||||
package cn.iocoder.yudao.module.ai.tool.function;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.ai.service.model.tool;
|
||||
package cn.iocoder.yudao.module.ai.tool.function;
|
||||
|
||||
import cn.iocoder.yudao.module.ai.util.AiUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
||||
@ -1,4 +1,4 @@
|
||||
package cn.iocoder.yudao.module.ai.service.model.tool;
|
||||
package cn.iocoder.yudao.module.ai.tool.function;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 参考 <a href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Tool Calling —— Methods as Tools</a>
|
||||
*/
|
||||
package cn.iocoder.yudao.module.ai.tool.function;
|
||||
@ -0,0 +1,19 @@
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
|
||||
/**
|
||||
* 来自 Spring AI 官方文档
|
||||
*
|
||||
* Represents a person with basic information.
|
||||
* This is an immutable record.
|
||||
*/
|
||||
public record Person(
|
||||
int id,
|
||||
String firstName,
|
||||
String lastName,
|
||||
String email,
|
||||
String sex,
|
||||
String ipAddress,
|
||||
String jobTitle,
|
||||
int age
|
||||
) {
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 来自 Spring AI 官方文档
|
||||
*
|
||||
* Service interface for managing Person data.
|
||||
* Defines the contract for CRUD operations and search/filter functionalities.
|
||||
*/
|
||||
public interface PersonService {
|
||||
|
||||
/**
|
||||
* Creates a new Person record.
|
||||
* Assigns a unique ID to the person and stores it.
|
||||
*
|
||||
* @param personData The data for the new person (ID field is ignored). Must not be null.
|
||||
* @return The created Person record, including the generated ID.
|
||||
*/
|
||||
Person createPerson(Person personData);
|
||||
|
||||
/**
|
||||
* Retrieves a Person by their unique ID.
|
||||
*
|
||||
* @param id The ID of the person to retrieve.
|
||||
* @return An Optional containing the found Person, or an empty Optional if not found.
|
||||
*/
|
||||
Optional<Person> getPersonById(int id);
|
||||
|
||||
/**
|
||||
* Retrieves all Person records currently stored.
|
||||
*
|
||||
* @return An unmodifiable List containing all Persons. Returns an empty list if none exist.
|
||||
*/
|
||||
List<Person> getAllPersons();
|
||||
|
||||
/**
|
||||
* Updates an existing Person record identified by ID.
|
||||
* Replaces the existing data with the provided data, keeping the original ID.
|
||||
*
|
||||
* @param id The ID of the person to update.
|
||||
* @param updatedPersonData The new data for the person (ID field is ignored). Must not be null.
|
||||
* @return true if the person was found and updated, false otherwise.
|
||||
*/
|
||||
boolean updatePerson(int id, Person updatedPersonData);
|
||||
|
||||
/**
|
||||
* Deletes a Person record identified by ID.
|
||||
*
|
||||
* @param id The ID of the person to delete.
|
||||
* @return true if the person was found and deleted, false otherwise.
|
||||
*/
|
||||
boolean deletePerson(int id);
|
||||
|
||||
/**
|
||||
* Searches for Persons whose job title contains the given query string (case-insensitive).
|
||||
*
|
||||
* @param jobTitleQuery The string to search for within job titles. Can be null or blank.
|
||||
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches or query is invalid.
|
||||
*/
|
||||
List<Person> searchByJobTitle(String jobTitleQuery);
|
||||
|
||||
/**
|
||||
* Filters Persons by their exact sex (case-insensitive).
|
||||
*
|
||||
* @param sex The sex to filter by (e.g., "Male", "Female"). Can be null or blank.
|
||||
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches or filter is invalid.
|
||||
*/
|
||||
List<Person> filterBySex(String sex);
|
||||
|
||||
/**
|
||||
* Filters Persons by their exact age.
|
||||
*
|
||||
* @param age The age to filter by.
|
||||
* @return An unmodifiable List of matching Persons. Returns an empty list if no matches.
|
||||
*/
|
||||
List<Person> filterByAge(int age);
|
||||
|
||||
}
|
||||
@ -0,0 +1,336 @@
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.tool.annotation.Tool;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 来自 Spring AI 官方文档
|
||||
*
|
||||
* Implementation of the PersonService interface using an in-memory data store.
|
||||
* Manages a collection of Person objects loaded from embedded CSV data.
|
||||
* This class is thread-safe due to the use of ConcurrentHashMap and AtomicInteger.
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class PersonServiceImpl implements PersonService {
|
||||
|
||||
private final Map<Integer, Person> personStore = new ConcurrentHashMap<>();
|
||||
|
||||
private AtomicInteger idGenerator;
|
||||
|
||||
/**
|
||||
* Embedded CSV data for initial population
|
||||
*/
|
||||
private static final String CSV_DATA = """
|
||||
Id,FirstName,LastName,Email,Sex,IpAddress,JobTitle,Age
|
||||
1,Fons,Tollfree,ftollfree0@senate.gov,Male,55.1 Tollfree Lane,Research Associate,31
|
||||
2,Emlynne,Tabourier,etabourier1@networksolutions.com,Female,18 Tabourier Way,Associate Professor,38
|
||||
3,Shae,Johncey,sjohncey2@yellowpages.com,Male,1 Johncey Circle,Structural Analysis Engineer,30
|
||||
4,Sebastien,Bradly,sbradly3@mapquest.com,Male,2 Bradly Hill,Chief Executive Officer,40
|
||||
5,Harriott,Kitteringham,hkitteringham4@typepad.com,Female,3 Kitteringham Drive,VP Sales,47
|
||||
6,Anallise,Parradine,aparradine5@miibeian.gov.cn,Female,4 Parradine Street,Analog Circuit Design manager,44
|
||||
7,Gorden,Kirkbright,gkirkbright6@reuters.com,Male,5 Kirkbright Plaza,Senior Editor,40
|
||||
8,Veradis,Ledwitch,vledwitch7@google.com.au,Female,6 Ledwitch Avenue,Computer Systems Analyst IV,44
|
||||
9,Agnesse,Penhalurick,apenhalurick8@google.it,Female,7 Penhalurick Terrace,Automation Specialist IV,41
|
||||
10,Bibby,Hutable,bhutable9@craigslist.org,Female,8 Hutable Place,Account Representative I,43
|
||||
11,Karoly,Lightoller,klightollera@rakuten.co.jp,Female,9 Lightoller Parkway,Senior Developer,46
|
||||
12,Cristine,Durrad,cdurradb@aol.com,Female,10 Durrad Center,Senior Developer,48
|
||||
13,Aggy,Napier,anapierc@hostgator.com,Female,11 Napier Court,VP Product Management,44
|
||||
14,Prisca,Caddens,pcaddensd@vinaora.com,Female,12 Caddens Alley,Business Systems Development Analyst,41
|
||||
15,Khalil,McKernan,kmckernane@google.fr,Male,13 McKernan Pass,Engineer IV,44
|
||||
16,Lorry,MacTrusty,lmactrustyf@eventbrite.com,Male,14 MacTrusty Junction,Design Engineer,42
|
||||
17,Casandra,Worsell,cworsellg@goo.gl,Female,15 Worsell Point,Systems Administrator IV,45
|
||||
18,Ulrikaumeko,Haveline,uhavelineh@usgs.gov,Female,16 Haveline Trail,Financial Advisor,42
|
||||
19,Shurlocke,Albany,salbanyi@artisteer.com,Male,17 Albany Plaza,Software Test Engineer III,46
|
||||
20,Myrilla,Brimilcombe,mbrimilcombej@accuweather.com,Female,18 Brimilcombe Road,Programmer Analyst I,48
|
||||
21,Carlina,Scimonelli,cscimonellik@va.gov,Female,19 Scimonelli Pass,Help Desk Technician,45
|
||||
22,Tina,Goullee,tgoulleel@miibeian.gov.cn,Female,20 Goullee Crossing,Accountant IV,43
|
||||
23,Adriaens,Storek,astorekm@devhub.com,Female,21 Storek Avenue,Recruiting Manager,40
|
||||
24,Tedra,Giraudot,tgiraudotn@wiley.com,Female,22 Giraudot Terrace,Speech Pathologist,47
|
||||
25,Josiah,Soares,jsoareso@google.nl,Male,23 Soares Street,Tax Accountant,45
|
||||
26,Kayle,Gaukrodge,kgaukrodgep@wikispaces.com,Female,24 Gaukrodge Parkway,Accountant II,43
|
||||
27,Ardys,Chuter,achuterq@ustream.tv,Female,25 Chuter Drive,Engineer IV,41
|
||||
28,Francyne,Baudinet,fbaudinetr@newyorker.com,Female,26 Baudinet Center,VP Accounting,48
|
||||
29,Gerick,Bullan,gbullans@seesaa.net,Male,27 Bullan Way,Senior Financial Analyst,43
|
||||
30,Northrup,Grivori,ngrivorit@unc.edu,Male,28 Grivori Plaza,Systems Administrator I,45
|
||||
31,Town,Duguid,tduguidu@squarespace.com,Male,29 Duguid Pass,Safety Technician IV,46
|
||||
32,Pierette,Kopisch,pkopischv@google.com.br,Female,30 Kopisch Lane,Director of Sales,41
|
||||
33,Jacquenetta,Le Prevost,jleprevostw@netlog.com,Female,31 Le Prevost Trail,Senior Developer,47
|
||||
34,Garvy,Rusted,grustedx@aboutads.info,Male,32 Rusted Junction,Senior Developer,42
|
||||
35,Clarice,Aysh,cayshy@merriam-webster.com,Female,33 Aysh Avenue,VP Quality Control,40
|
||||
36,Tracie,Fedorski,tfedorskiz@bloglines.com,Male,34 Fedorski Terrace,Design Engineer,44
|
||||
37,Noelyn,Matushenko,nmatushenko10@globo.com,Female,35 Matushenko Place,VP Sales,48
|
||||
38,Rudiger,Klaesson,rklaesson11@usnews.com,Male,36 Klaesson Road,Database Administrator IV,43
|
||||
39,Mirella,Syddie,msyddie12@geocities.jp,Female,37 Syddie Circle,Geological Engineer,46
|
||||
40,Donalt,O'Lunny,dolunny13@elpais.com,Male,38 O'Lunny Center,Analog Circuit Design manager,41
|
||||
41,Guntar,Deniskevich,gdeniskevich14@google.com.hk,Male,39 Deniskevich Way,Structural Engineer,47
|
||||
42,Hort,Shufflebotham,hshufflebotham15@about.me,Male,40 Shufflebotham Court,Structural Analysis Engineer,45
|
||||
43,Dominique,Thickett,dthickett16@slashdot.org,Male,41 Thickett Crossing,Safety Technician I,42
|
||||
44,Zebulen,Piscopello,zpiscopello17@umich.edu,Male,42 Piscopello Parkway,Web Developer II,40
|
||||
45,Mellicent,Mac Giany,mmacgiany18@state.tx.us,Female,43 Mac Giany Pass,Assistant Manager,44
|
||||
46,Merle,Bounds,mbounds19@amazon.co.jp,Female,44 Bounds Alley,Systems Administrator III,41
|
||||
47,Madelle,Farbrace,mfarbrace1a@xinhuanet.com,Female,45 Farbrace Terrace,Quality Engineer,48
|
||||
48,Galvin,O'Sheeryne,gosheeryne1b@addtoany.com,Male,46 O'Sheeryne Way,Environmental Specialist,43
|
||||
49,Guillemette,Bootherstone,gbootherstone1c@nationalgeographic.com,Female,47 Bootherstone Plaza,Professor,46
|
||||
50,Letti,Aylmore,laylmore1d@vinaora.com,Female,48 Aylmore Circle,Automation Specialist I,40
|
||||
51,Nonie,Rivalland,nrivalland1e@weather.com,Female,49 Rivalland Avenue,Software Test Engineer IV,45
|
||||
52,Jacquelynn,Halfacre,jhalfacre1f@surveymonkey.com,Female,50 Halfacre Pass,Geologist II,42
|
||||
53,Anderea,MacKibbon,amackibbon1g@weibo.com,Female,51 MacKibbon Parkway,Automation Specialist II,47
|
||||
54,Wash,Klimko,wklimko1h@slashdot.org,Male,52 Klimko Alley,Database Administrator I,40
|
||||
55,Flori,Kynett,fkynett1i@auda.org.au,Female,53 Kynett Trail,Quality Control Specialist,46
|
||||
56,Libbey,Penswick,lpenswick1j@google.co.uk,Female,54 Penswick Point,VP Accounting,43
|
||||
57,Silvanus,Skellorne,sskellorne1k@booking.com,Male,55 Skellorne Drive,Account Executive,48
|
||||
58,Carmine,Mateos,cmateos1l@plala.or.jp,Male,56 Mateos Terrace,Systems Administrator I,41
|
||||
59,Sheffie,Blazewicz,sblazewicz1m@google.com.au,Male,57 Blazewicz Center,VP Sales,44
|
||||
60,Leanor,Worsnop,lworsnop1n@uol.com.br,Female,58 Worsnop Plaza,Systems Administrator III,45
|
||||
61,Caspar,Pamment,cpamment1o@google.co.jp,Male,59 Pamment Court,Senior Financial Analyst,42
|
||||
62,Justinian,Pentycost,jpentycost1p@sciencedaily.com,Male,60 Pentycost Way,Senior Quality Engineer,47
|
||||
63,Gerianne,Jarnell,gjarnell1q@bing.com,Female,61 Jarnell Avenue,Help Desk Operator,40
|
||||
64,Boycie,Zanetto,bzanetto1r@about.com,Male,62 Zanetto Place,Quality Engineer,46
|
||||
65,Camilla,Mac Giany,cmacgiany1s@state.gov,Female,63 Mac Giany Parkway,Senior Cost Accountant,43
|
||||
66,Hadlee,Piscopiello,hpiscopiello1t@artisteer.com,Male,64 Piscopiello Street,Account Representative III,48
|
||||
67,Bobbie,Penvarden,bpenvarden1u@google.cn,Male,65 Penvarden Lane,Help Desk Operator,41
|
||||
68,Ali,Gowlett,agowlett1v@parallels.com,Male,66 Gowlett Pass,VP Marketing,44
|
||||
69,Olivette,Acome,oacome1w@qq.com,Female,67 Acome Hill,VP Product Management,45
|
||||
70,Jehanna,Brotherheed,jbrotherheed1x@google.nl,Female,68 Brotherheed Junction,Database Administrator III,42
|
||||
71,Morgan,Berthomieu,mberthomieu1y@artisteer.com,Male,69 Berthomieu Alley,Systems Administrator II,47
|
||||
72,Linzy,Shilladay,lshilladay1z@icq.com,Female,70 Shilladay Trail,Research Assistant IV,40
|
||||
73,Faydra,Brimner,fbrimner20@mozilla.org,Female,71 Brimner Road,Senior Editor,46
|
||||
74,Gwenore,Oxlee,goxlee21@devhub.com,Female,72 Oxlee Terrace,Systems Administrator II,43
|
||||
75,Evangelin,Beinke,ebeinke22@mozilla.com,Female,73 Beinke Circle,Accountant I,48
|
||||
76,Missy,Cockling,mcockling23@si.edu,Female,74 Cockling Way,Software Engineer I,41
|
||||
77,Suzanne,Klimschak,sklimschak24@etsy.com,Female,75 Klimschak Plaza,Tax Accountant,44
|
||||
78,Candide,Goricke,cgoricke25@weebly.com,Female,76 Goricke Pass,Sales Associate,45
|
||||
79,Gerome,Pinsent,gpinsent26@google.com.au,Male,77 Pinsent Junction,Software Consultant,42
|
||||
80,Lezley,Mac Giany,lmacgiany27@scribd.com,Male,78 Mac Giany Alley,Operator,47
|
||||
81,Tobiah,Durn,tdurn28@state.tx.us,Male,79 Durn Court,VP Sales,40
|
||||
82,Sherlocke,Cockshoot,scockshoot29@yelp.com,Male,80 Cockshoot Street,Senior Financial Analyst,46
|
||||
83,Myrle,Speenden,mspeenden2a@utexas.edu,Female,81 Speenden Center,Senior Developer,43
|
||||
84,Isidore,Gorries,igorries2b@flavors.me,Male,82 Gorries Parkway,Sales Representative,48
|
||||
85,Isac,Kitchingman,ikitchingman2c@businessinsider.com,Male,83 Kitchingman Drive,VP Accounting,41
|
||||
86,Benedetta,Purrier,bpurrier2d@admin.ch,Female,84 Purrier Trail,VP Accounting,44
|
||||
87,Tera,Fitchell,tfitchell2e@fotki.com,Female,85 Fitchell Place,Software Engineer IV,45
|
||||
88,Abbe,Pamment,apamment2f@about.com,Male,86 Pamment Avenue,VP Sales,42
|
||||
89,Jandy,Gommowe,jgommowe2g@angelfire.com,Female,87 Gommowe Road,Financial Analyst,47
|
||||
90,Karena,Fussey,kfussey2h@google.com.au,Female,88 Fussey Point,Assistant Professor,40
|
||||
91,Gaspar,Pammenter,gpammenter2i@google.com.br,Male,89 Pammenter Hill,Help Desk Operator,46
|
||||
92,Stanwood,Mac Giany,smacgiany2j@prlog.org,Male,90 Mac Giany Terrace,Research Associate,43
|
||||
93,Byrom,Beedell,bbeedell2k@google.co.jp,Male,91 Beedell Way,VP Sales,48
|
||||
94,Annabella,Rowbottom,arowbottom2l@google.com.au,Female,92 Rowbottom Plaza,Help Desk Operator,41
|
||||
95,Rodolphe,Debell,rdebell2m@imageshack.us,Male,93 Debell Pass,Design Engineer,44
|
||||
96,Tyne,Gommey,tgommey2n@joomla.org,Female,94 Gommey Junction,VP Marketing,45
|
||||
97,Christoper,Pincked,cpincked2o@icq.com,Male,95 Pincked Alley,Human Resources Manager,42
|
||||
98,Kore,Le Prevost,kleprevost2p@tripadvisor.com,Female,96 Le Prevost Street,VP Quality Control,47
|
||||
99,Ceciley,Petrolli,cpetrolli2q@oaic.gov.au,Female,97 Petrolli Court,Senior Developer,40
|
||||
100,Elspeth,Mac Giany,emacgiany2r@icio.us,Female,98 Mac Giany Parkway,Internal Auditor,46
|
||||
""";
|
||||
|
||||
/**
|
||||
* Initializes the service after dependency injection by loading data from the CSV string.
|
||||
* Uses @PostConstruct to ensure this runs after the bean is created.
|
||||
*/
|
||||
@PostConstruct
|
||||
private void initializeData() {
|
||||
log.info("Initializing PersonService data store...");
|
||||
int maxId = loadDataFromCsv();
|
||||
idGenerator = new AtomicInteger(maxId);
|
||||
log.info("PersonService initialized with {} records. Next ID: {}", personStore.size(), idGenerator.get() + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the embedded CSV data and populates the in-memory store.
|
||||
* Calculates the maximum ID found in the data to initialize the ID generator.
|
||||
*
|
||||
* @return The maximum ID found in the loaded CSV data.
|
||||
*/
|
||||
private int loadDataFromCsv() {
|
||||
final AtomicInteger currentMaxId = new AtomicInteger(0);
|
||||
// Clear existing data before loading (important for tests or re-initialization scenarios)
|
||||
personStore.clear();
|
||||
try (Stream<String> lines = CSV_DATA.lines().skip(1)) { // Skip header row
|
||||
lines.forEach(line -> {
|
||||
try {
|
||||
// Split carefully, handling potential commas within quoted fields if necessary (simple split here)
|
||||
String[] fields = line.split(",", 8); // Limit split to handle potential commas in job title
|
||||
if (fields.length == 8) {
|
||||
int id = Integer.parseInt(fields[0].trim());
|
||||
String firstName = fields[1].trim();
|
||||
String lastName = fields[2].trim();
|
||||
String email = fields[3].trim();
|
||||
String sex = fields[4].trim();
|
||||
String ipAddress = fields[5].trim();
|
||||
String jobTitle = fields[6].trim();
|
||||
int age = Integer.parseInt(fields[7].trim());
|
||||
|
||||
Person person = new Person(id, firstName, lastName, email, sex, ipAddress, jobTitle, age);
|
||||
personStore.put(id, person);
|
||||
currentMaxId.updateAndGet(max -> Math.max(max, id));
|
||||
} else {
|
||||
log.warn("Skipping malformed CSV line (expected 8 fields, found {}): {}", fields.length, line);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.warn("Skipping line due to parsing error (ID or Age): {} - Error: {}", line, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("Skipping line due to unexpected error: {} - Error: {}", line, e.getMessage(), e);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("Fatal error reading embedded CSV data: {}", e.getMessage(), e);
|
||||
// In a real application, might throw a specific initialization exception
|
||||
}
|
||||
return currentMaxId.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_create_person",
|
||||
description = "Create a new person record in the in-memory store."
|
||||
)
|
||||
public Person createPerson(Person personData) {
|
||||
if (personData == null) {
|
||||
throw new IllegalArgumentException("Person data cannot be null");
|
||||
}
|
||||
int newId = idGenerator.incrementAndGet();
|
||||
// Create a new Person record using data from the input, but with the generated ID
|
||||
Person newPerson = new Person(
|
||||
newId,
|
||||
personData.firstName(),
|
||||
personData.lastName(),
|
||||
personData.email(),
|
||||
personData.sex(),
|
||||
personData.ipAddress(),
|
||||
personData.jobTitle(),
|
||||
personData.age()
|
||||
);
|
||||
personStore.put(newId, newPerson);
|
||||
log.debug("Created person: {}", newPerson);
|
||||
return newPerson;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_get_person_by_id",
|
||||
description = "Retrieve a person record by ID from the in-memory store."
|
||||
)
|
||||
public Optional<Person> getPersonById(int id) {
|
||||
Person person = personStore.get(id);
|
||||
log.debug("Retrieved person by ID {}: {}", id, person);
|
||||
return Optional.ofNullable(person);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_get_all_persons",
|
||||
description = "Retrieve all person records from the in-memory store."
|
||||
)
|
||||
public List<Person> getAllPersons() {
|
||||
// Return an unmodifiable view of the values
|
||||
List<Person> allPersons = personStore.values().stream().toList();
|
||||
log.debug("Retrieved all persons (count: {})", allPersons.size());
|
||||
return allPersons;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_update_person",
|
||||
description = "Update an existing person record by ID in the in-memory store."
|
||||
)
|
||||
public boolean updatePerson(int id, Person updatedPersonData) {
|
||||
if (updatedPersonData == null) {
|
||||
throw new IllegalArgumentException("Updated person data cannot be null");
|
||||
}
|
||||
// Use computeIfPresent for atomic update if the key exists
|
||||
Person result = personStore.computeIfPresent(id, (key, existingPerson) ->
|
||||
// Create a new Person record with the original ID but updated data
|
||||
new Person(
|
||||
id, // Keep original ID
|
||||
updatedPersonData.firstName(),
|
||||
updatedPersonData.lastName(),
|
||||
updatedPersonData.email(),
|
||||
updatedPersonData.sex(),
|
||||
updatedPersonData.ipAddress(),
|
||||
updatedPersonData.jobTitle(),
|
||||
updatedPersonData.age()
|
||||
)
|
||||
);
|
||||
boolean updated = result != null;
|
||||
log.debug("Update attempt for ID {}: {}", id, updated ? "Successful" : "Failed (Not Found)");
|
||||
if(updated) log.trace("Updated person data for ID {}: {}", id, result);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_delete_person",
|
||||
description = "Delete a person record by ID from the in-memory store."
|
||||
)
|
||||
public boolean deletePerson(int id) {
|
||||
boolean removed = personStore.remove(id) != null;
|
||||
log.debug("Delete attempt for ID {}: {}", id, removed ? "Successful" : "Failed (Not Found)");
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_search_by_job_title",
|
||||
description = "Search for persons by job title in the in-memory store."
|
||||
)
|
||||
public List<Person> searchByJobTitle(String jobTitleQuery) {
|
||||
if (jobTitleQuery == null || jobTitleQuery.isBlank()) {
|
||||
log.debug("Search by job title skipped due to blank query.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String lowerCaseQuery = jobTitleQuery.toLowerCase();
|
||||
List<Person> results = personStore.values().stream()
|
||||
.filter(person -> person.jobTitle() != null && person.jobTitle().toLowerCase().contains(lowerCaseQuery))
|
||||
.collect(Collectors.toList());
|
||||
log.debug("Search by job title '{}' found {} results.", jobTitleQuery, results.size());
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_filter_by_sex",
|
||||
description = "Filters Persons by sex (case-insensitive)."
|
||||
)
|
||||
public List<Person> filterBySex(String sex) {
|
||||
if (sex == null || sex.isBlank()) {
|
||||
log.debug("Filter by sex skipped due to blank filter.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<Person> results = personStore.values().stream()
|
||||
.filter(person -> person.sex() != null && person.sex().equalsIgnoreCase(sex))
|
||||
.collect(Collectors.toList());
|
||||
log.debug("Filter by sex '{}' found {} results.", sex, results.size());
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Tool(
|
||||
name = "ps_filter_by_age",
|
||||
description = "Filters Persons by age."
|
||||
)
|
||||
public List<Person> filterByAge(int age) {
|
||||
if (age < 0) {
|
||||
log.debug("Filter by age skipped due to negative age: {}", age);
|
||||
return Collections.emptyList(); // Or throw IllegalArgumentException based on requirements
|
||||
}
|
||||
List<Person> results = personStore.values().stream()
|
||||
.filter(person -> person.age() == age)
|
||||
.collect(Collectors.toList());
|
||||
log.debug("Filter by age {} found {} results.", age, results.size());
|
||||
return Collections.unmodifiableList(results);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* 参考 <a href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Tool Calling —— Methods as Tools</a>
|
||||
*/
|
||||
package cn.iocoder.yudao.module.ai.tool.method;
|
||||
@ -196,6 +196,13 @@ spring:
|
||||
model: deepseek-chat
|
||||
model:
|
||||
rerank: false # 是否开启“通义千问”的 Rerank 模型,填写 dashscope 开启
|
||||
mcp:
|
||||
server:
|
||||
enabled: true
|
||||
name: yudao-mcp-server
|
||||
version: 1.0.0
|
||||
instructions: 一个 MCP 示例服务
|
||||
sse-endpoint: /sse
|
||||
|
||||
yudao:
|
||||
ai:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user