约 5475 个字 896 行代码 预计阅读时间 29 分钟
多模态 多模态介绍 多模态性指模型同时理解和处理文本、图像、音频及其他数据格式等多源信息的能力。
人类通过多模态数据输入并融合处理知识。我们的学习方式和体验都是多模态的 — 不只有视觉、听觉或文本的单一感知。
机器学习往往专注于处理单一模态的专业模型。例如,我们开发音频模型用于文本转语音或语音转文本任务,开发计算机视觉模型用于目标检测和分类等任务。
然而,新一代多模态大语言模型正在兴起。这些模型能接受文本、图像、音频和视频等多种输入,并通过整合这些输出生成文本响应
想象一下人类是怎么认识世界的?
看:你能看到风景、人脸、文字、颜色(视觉) 听:你能听到说话声、音乐、鸟叫、汽车轰鸣(听觉) 闻:你能闻到花香、饭菜(嗅觉) 尝:你能尝出酸甜苦辣咸(味觉) 摸:你能感觉到冷热、软硬、粗糙光滑(触觉) 说/写:你能用语言文字描述你的想法(语言) 这些不同的方式(看、听、闻、尝、摸、说),每一种就是一种"模态"(Modality)
"多模态"(Multimodal)的意思就是:同时使用多种不同的"模态"来理解和表达信息
从人类到人工智能:
传统AI:器官"单一"的机器,例如:
聊天机器人:只处理文字,看不懂图,听不懂语音 语音助手:只能听你说话,回答或执行命令,看不懂图片什么意思 多模态AI:同时具备"看"、"读"、"听"、"说"(甚至更多)两种及以上的能力,并把这些信息融合起来理解,例如:
看图说话:给他一张照片,他可以理解图片内容,并使用文字/语音描述出来 图片结合问答:给他一张照片和一些关于图片的问题,他能够结合图片和问题进行回答 图像模型介绍 基本使用 图像模型(Image Model)是专注于处理与理解视觉数据的人工智能模型,是计算机视觉与多模态学习的核心。主要分为两类:
图像生成模型:根据文本、图像等条件输入,合成新的图像 图像理解模型:对输入图像进行分析,完成分类、检测、分割等认知任务 在Spring AI中也有对图像模型的支持,具体参考官方文档 ,下面针对百度的千帆模型给出一个示例和介绍
为了可以使用到百度千帆模型,需要在百度千帆 申请API Key。接着创建项目引入依赖,此处依旧是参考Spring AI介绍与基础使用 中的依赖,接着编写配置文件如下:
YAML spring :
ai :
openai :
api-key : xxx
base-url : https://qianfan.baidubce.com
image :
options :
model : "qwen-image"
imagesPath : /v2/images/generations
Note
需要注意的是,imagesPath比较特殊,在官方文档上并没有具体说明,但是因为百度更新过模型请求地址的版本,导致OpenAI默认的/v1/images/generations地址是不能用的
接着编写请求接口:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 @RestController
@RequestMapping ( "/qianfan" )
public class ChatController {
@Autowired
private OpenAiImageModel openAiImageModel ;
@RequestMapping ( "/image" )
public void image () {
// 输入提示词以及设置图片参数
ImageResponse imageResponse = openAiImageModel . call (
new ImagePrompt ( "帮我生成一张小猫站着的图片" ,
OpenAiImageOptions . builder ()
. quality ( "hd" )
. N ( 1 )
. height ( 1024 )
. width ( 1024 ). build ()));
// 获取图片URL
System . out . println ( imageResponse . getResult (). getOutput (). getUrl ());
}
}
运行程序请求上面的地址即可获取到生成成功后的图片URL,点击访问即可在浏览器中看到图片
API介绍 在上面的示例代码中,使用到了OpenAiImageModel类,这个类实际上是OpenAI公司根据Spring的接口自行实现的类,其实现的接口为ImageModel,这个接口的定义如下:
Java @FunctionalInterface
public interface ImageModel extends Model < ImagePrompt , ImageResponse > {
ImageResponse call ( ImagePrompt request );
}
在call()方法中,需要传递一个ImagePrompt对象,这个对象可以理解为给模型的请求参数,该类实现如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class ImagePrompt implements ModelRequest < List < ImageMessage >> {
private final List < ImageMessage > messages ;
private ImageOptions imageModelOptions ;
public ImagePrompt ( List < ImageMessage > messages ) {
this . messages = messages ;
}
public ImagePrompt ( List < ImageMessage > messages , ImageOptions imageModelOptions ) {
this . messages = messages ;
this . imageModelOptions = imageModelOptions ;
}
public ImagePrompt ( ImageMessage imageMessage , ImageOptions imageOptions ) {
this ( Collections . singletonList ( imageMessage ), imageOptions );
}
public ImagePrompt ( String instructions , ImageOptions imageOptions ) {
this ( new ImageMessage ( instructions ), imageOptions );
}
public ImagePrompt ( String instructions ) {
this ( new ImageMessage ( instructions ), ImageOptionsBuilder . builder (). build ());
}
public List < ImageMessage > getInstructions () {
return this . messages ;
}
public ImageOptions getOptions () {
return this . imageModelOptions ;
}
public String toString () {
String var10000 = String . valueOf ( this . messages );
return "NewImagePrompt{messages=" + var10000 + ", imageModelOptions=" + String . valueOf ( this . imageModelOptions ) + "}" ;
}
public boolean equals ( Object o ) {
if ( this == o ) {
return true ;
} else if ( ! ( o instanceof ImagePrompt )) {
return false ;
} else {
ImagePrompt that = ( ImagePrompt ) o ;
return Objects . equals ( this . messages , that . messages ) && Objects . equals ( this . imageModelOptions , that . imageModelOptions );
}
}
public int hashCode () {
return Objects . hash ( new Object [] { this . messages , this . imageModelOptions });
}
}
其中ImageMessage代表图片消息,里面包含了给图片设置的相关信息,在请求时会通过这个类告诉模型图片需要的参数,具体实现如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 public class ImageMessage {
private String text ;
private Float weight ;
public ImageMessage ( String text ) {
this . text = text ;
}
public ImageMessage ( String text , Float weight ) {
this . text = text ;
this . weight = weight ;
}
public String getText () {
return this . text ;
}
public Float getWeight () {
return this . weight ;
}
public String toString () {
return "ImageMessage{text='" + this . text + "', weight=" + this . weight + "}" ;
}
public boolean equals ( Object o ) {
if ( this == o ) {
return true ;
} else if ( ! ( o instanceof ImageMessage )) {
return false ;
} else {
ImageMessage that = ( ImageMessage ) o ;
return Objects . equals ( this . text , that . text ) && Objects . equals ( this . weight , that . weight );
}
}
public int hashCode () {
return Objects . hash ( new Object [] { this . text , this . weight });
}
}
其中text表示提示词文字,weight表示权重
接着是除图片基本消息以外的图片选项接口ImageOptions,具体实现如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 public interface ImageOptions extends ModelOptions {
@Nullable
Integer getN ();
@Nullable
String getModel ();
@Nullable
Integer getWidth ();
@Nullable
Integer getHeight ();
@Nullable
String getResponseFormat ();
@Nullable
String getStyle ();
}
对于OpenAI的模型来说,其有一个实现了ImageModel的类OpenAiImageOptions,其部分实现如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 @JsonInclude ( Include . NON_NULL )
public class OpenAiImageOptions implements ImageOptions {
@JsonProperty ( "n" )
private Integer n ;
@JsonProperty ( "model" )
private String model ;
@JsonProperty ( "size_width" )
private Integer width ;
@JsonProperty ( "size_height" )
private Integer height ;
@JsonProperty ( "quality" )
private String quality ;
@JsonProperty ( "response_format" )
private String responseFormat ;
@JsonProperty ( "size" )
private String size ;
@JsonProperty ( "style" )
private String style ;
@JsonProperty ( "user" )
private String user ;
public static Builder builder () {
return new Builder ();
}
// ...
}
对于其中的参数,在官方文档 中也有具体的介绍,此处给出中文翻译的参考版本:
属性 说明 默认值 spring.ai.openai.image.enabled(已移除且不再有效) 启用OpenAI图像模型 true spring.ai.model.image 启用OpenAI图像模型 openai spring.ai.openai.image.base-url 可选覆盖spring.ai.openai.base-url,用于提供图像专用URL - spring.ai.openai.image.api-key 可选覆盖spring.ai.openai.api-key,用于提供图像专用API Key - spring.ai.openai.image.organization-id 可选指定某次API请求使用的组织(organization) - spring.ai.openai.image.project-id 可选指定某次API请求使用的项目(project) - spring.ai.openai.image.options.n 要生成的图片数量,必须在1到10之间;对于dall-e-3仅支持n=1 - spring.ai.openai.image.options.model 用于图像生成的模型 OpenAiImageApi.DEFAULT_IMAGE_MODEL spring.ai.openai.image.options.quality 生成图像质量,hd会产生更精细细节和更高一致性,该参数仅dall-e-3支持 - spring.ai.openai.image.options.response_format 返回生成图像的格式,必须是URL或b64_json - spring.ai.openai.image.options.size 生成图像尺寸:dall-e-2支持256x256、512x512、1024x1024;dall-e-3支持1024x1024、1792x1024、1024x1792 - spring.ai.openai.image.options.size_width 生成图像宽度,dall-e-2必须为256、512或1024 - spring.ai.openai.image.options.size_height 生成图像高度,dall-e-2必须为256、512或1024 - spring.ai.openai.image.options.style 生成图像风格,必须是vivid或natural,vivid偏超写实和戏剧化,natural更自然不过度写实,该参数仅dall-e-3支持 - spring.ai.openai.image.options.user 代表终端用户的唯一标识,可帮助OpenAI监控并检测滥用 -
其中,前缀可以在OpenAiImageAutoConfiguration类中找到:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 @AutoConfiguration (
after = { RestClientAutoConfiguration . class , WebClientAutoConfiguration . class , SpringAiRetryAutoConfiguration . class }
)
@ConditionalOnClass ({ OpenAiApi . class })
@ConditionalOnProperty (
name = { "spring.ai.model.image" },
havingValue = "openai" ,
matchIfMissing = true
)
@EnableConfigurationProperties ({ OpenAiConnectionProperties . class , OpenAiImageProperties . class })
public class OpenAiImageAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OpenAiImageModel openAiImageModel ( OpenAiConnectionProperties commonProperties , OpenAiImageProperties imageProperties , ObjectProvider < RestClient . Builder > restClientBuilderProvider , RetryTemplate retryTemplate , ResponseErrorHandler responseErrorHandler , ObjectProvider < ObservationRegistry > observationRegistry , ObjectProvider < ImageModelObservationConvention > observationConvention ) {
OpenAIAutoConfigurationUtil . ResolvedConnectionProperties resolved = OpenAIAutoConfigurationUtil . resolveConnectionProperties ( commonProperties , imageProperties , "image" );
OpenAiImageApi openAiImageApi = OpenAiImageApi . builder (). baseUrl ( resolved . baseUrl ()). apiKey ( new SimpleApiKey ( resolved . apiKey ())). headers ( resolved . headers ()). imagesPath ( imageProperties . getImagesPath ()). restClientBuilder (( RestClient . Builder ) restClientBuilderProvider . getIfAvailable ( RestClient :: builder )). responseErrorHandler ( responseErrorHandler ). build ();
OpenAiImageModel imageModel = new OpenAiImageModel ( openAiImageApi , imageProperties . getOptions (), retryTemplate , ( ObservationRegistry ) observationRegistry . getIfUnique (() -> ObservationRegistry . NOOP ));
Objects . requireNonNull ( imageModel );
observationConvention . ifAvailable ( imageModel :: setObservationConvention );
return imageModel ;
}
}
在OpenAiImageProperties就可以找到CONFIG_PREFIX字段,这个字段就代表前缀:
Java @ConfigurationProperties ( "spring.ai.openai.image" )
public class OpenAiImageProperties extends OpenAiParentProperties {
public static final String CONFIG_PREFIX = "spring.ai.openai.image" ;
public static final String DEFAULT_IMAGES_PATH = "v1/images/generations" ;
private String imagesPath = "v1/images/generations" ;
public static final String DEFAULT_IMAGE_MODEL ;
@NestedConfigurationProperty
private final OpenAiImageOptions options ;
// ...
}
然后进入OpenAiImageOptions找到需要配置的属性即可
了解完方法的参数,接下来介绍方法的返回值ImageResponse。ImageResponse封装AI模型的生成结果,具体实现如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 public class ImageResponse implements ModelResponse < ImageGeneration > {
private final ImageResponseMetadata imageResponseMetadata ;
private final List < ImageGeneration > imageGenerations ;
public ImageResponse ( List < ImageGeneration > generations ) {
this ( generations , new ImageResponseMetadata ());
}
public ImageResponse ( List < ImageGeneration > generations , ImageResponseMetadata imageResponseMetadata ) {
this . imageResponseMetadata = imageResponseMetadata ;
this . imageGenerations = List . copyOf ( generations );
}
public List < ImageGeneration > getResults () {
return this . imageGenerations ;
}
public ImageGeneration getResult () {
return CollectionUtils . isEmpty ( this . imageGenerations ) ? null : ( ImageGeneration ) this . imageGenerations . get ( 0 );
}
public ImageResponseMetadata getMetadata () {
return this . imageResponseMetadata ;
}
public String toString () {
String var10000 = String . valueOf ( this . imageResponseMetadata );
return "ImageResponse [imageResponseMetadata=" + var10000 + ", imageGenerations=" + String . valueOf ( this . imageGenerations ) + "]" ;
}
public boolean equals ( Object o ) {
if ( this == o ) {
return true ;
} else if ( ! ( o instanceof ImageResponse )) {
return false ;
} else {
ImageResponse that = ( ImageResponse ) o ;
return Objects . equals ( this . imageResponseMetadata , that . imageResponseMetadata ) && Objects . equals ( this . imageGenerations , that . imageGenerations );
}
}
public int hashCode () {
return Objects . hash ( new Object [] { this . imageResponseMetadata , this . imageGenerations });
}
}
其中的ImageGeneration类存储图片结果,具体实现如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class ImageGeneration implements ModelResult < Image > {
private ImageGenerationMetadata imageGenerationMetadata ;
private Image image ;
public ImageGeneration ( Image image ) {
this . image = image ;
}
public ImageGeneration ( Image image , ImageGenerationMetadata imageGenerationMetadata ) {
this . image = image ;
this . imageGenerationMetadata = imageGenerationMetadata ;
}
public Image getOutput () {
return this . image ;
}
public ImageGenerationMetadata getMetadata () {
return this . imageGenerationMetadata ;
}
public String toString () {
String var10000 = String . valueOf ( this . imageGenerationMetadata );
return "ImageGeneration{imageGenerationMetadata=" + var10000 + ", image=" + String . valueOf ( this . image ) + "}" ;
}
}
应用启动时OpenAiImageAutoConfiguration先按spring.ai.openai.*与spring.ai.openai.image.*装配出OpenAiImageModel和默认参数,接口被调用后业务代码构造ImagePrompt,其中ImageMessage承载提示词、OpenAiImageOptions承载如model、n、quality、size或width/height等选项,随后执行openAiImageModel.call(...),框架把默认配置与本次请求参数合并并转换为供应商图片生成HTTP请求发送到images/generations端点(如OpenAI常见/v1/images/generations,示例里的千帆是/v2/images/generations),模型生成后返回包含url或b64_json的结果JSON,Spring AI再反序列化为统一的ImageResponse对象,你在业务层通过imageResponse.getResult().getOutput()取到首张图的输出(如URL)并返回给前端或调用方,同时错误处理与重试由底层的ResponseErrorHandler和RetryTemplate负责贯穿整个调用过程
Note
后续的其他多模态模型的API也可以按照类似上面的思路进行理解
Spring AI Alibaba介绍与基础使用 Spring AI Alibaba概述 随着生成式AI的快速发展,基于AI开发框架构建AI应用的诉求迅速增长,涌现出了包括LangChain、LlamaIndex等开发框架,它们为Python开发者提供了方便的API抽象。但这些开发框架对于国内习惯了Spring开发范式的Java开发者来说,并不十分友好和丝滑。因此,我们基于Spring AI发布并快速演进Spring AI Alibaba,通过提供一种方便的API抽象,帮助Java开发者简化AI应用的开发,一步迈入AI原生时代
Spring AI Alibaba开源项目基于Spring AI构建,是阿里云通义系列模型及服务在Java AI应用开发领域的API抽象与云原生基础设施集成方案,帮助开发者快速构建AI应用
Spring AI Alibaba作为开发AI应用程序的基础框架,定义了以下抽象概念与API,并提供了API与通义系列模型的适配:
开发复杂AI应用的高阶抽象Fluent API — ChatClient 提供多种大模型服务对接能力,包括主流开源与阿里云通义大模型服务(百炼)等 支持的模型类型包括聊天、文生图、音频转录、语音合成等 支持同步和流式API,在保持应用层API不变的情况下支持灵活切换底层模型服务,支持特定模型的定制化能力(参数传递) 支持Structured Output,即将AI模型输出映射到POJOs 支持向量数据库存储与检索 支持函数调用Function Calling 支持构建AI Agent所需要的工具调用和对话内存记忆能力 支持RAG开发模式,包括离线文档处理如DocumentReader、Splitter、Embedding、VectorStore等,支持Retrieve检索 快速上手 Spring AI Alibaba实现了与阿里云通义模型的完整适配,接下来学习如何使用Spring AI Alibaba基于通义模型服务进行智能聊天
因为Spring AI Alibaba基于Spring Boot 3.x开发,因此本地JDK版本要求为17及以上
阿里云的模型服务平台百炼是一站式的大模型开发及应用构建平台。我们可以借助百炼平台,调用大模型,与大模型对话,实现内容创作、摘要生成等。当需要通过API或SDK方式调用大模型及应用时,需要获取一个合法的API-KEY并设置AI_DASHSCOPE_API_KEY环境变量
可参考:如何获取API Key_模型服务平台百炼(Model Studio)-阿里云帮助中心
接着引入Spring AI Alibaba的依赖:
XML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 <dependencyManagement>
<dependencies>
<dependency>
<groupId> com.alibaba.cloud.ai</groupId>
<artifactId> spring-ai-alibaba-bom</artifactId>
<version> 1.1.2.0</version>
<type> pom</type>
<scope> import</scope>
</dependency>
<dependency>
<groupId> org.springframework.ai</groupId>
<artifactId> spring-ai-bom</artifactId>
<version> 1.1.2</version>
<type> pom</type>
<scope> import</scope>
</dependency>
<dependency>
<groupId> com.alibaba.cloud.ai</groupId>
<artifactId> spring-ai-alibaba-extensions-bom</artifactId>
<version> 1.1.2.1</version>
<type> pom</type>
<scope> import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId> com.alibaba.cloud.ai</groupId>
<artifactId> spring-ai-alibaba-agent-framework</artifactId>
</dependency>
<dependency>
<groupId> com.alibaba.cloud.ai</groupId>
<artifactId> spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
</dependencies>
需要注意,根据Spring AI Alibaba官方文档的版本说明 ,1.1.2.0版本要求Spring Boot版本最低为3.5.x
下面编写配置文件application.yml:
YAML spring :
ai :
dashscope :
api-key : sk-xxx
编写基本的聊天示例:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 @RequestMapping ( "/ali" )
@RestController
public class AliController {
private static final String DEFAULT_PROMPT = "你是一个博学的智能聊天助手,请根据用户提问回答!" ;
private final ChatClient dashScopeChatClient ;
public AliController ( ChatClient . Builder chatClientBuilder ) {
this . dashScopeChatClient = chatClientBuilder
. defaultSystem ( DEFAULT_PROMPT )
. defaultAdvisors ( new SimpleLoggerAdvisor ()) // 实现Logger的Advisor
. build ();
}
@GetMapping ( "/chat" )
public String chat ( String message ) {
return dashScopeChatClient . prompt ( message ). call (). content ();
}
}
ChatClient Spring AI Alibaba是基于Spring AI进行构建的。所以Spring AI ChatClient具备的功能,Spring AI Alibaba大多也具备,如流式响应、返回实体类等
具体参考:Chat Client-阿里云Spring AI Alibaba官方官网
流式响应 Java Flux < String > output = chatClient . prompt ()
. user ( "Tell me a joke" )
. stream ()
. content ();
返回实体类 Java record ActorFilms ( String actor , List < String > movies ) {
}
ActorFilms actorFilms = chatClient . prompt ()
. user ( "Generate the filmography for a random actor." )
. call ()
. entity ( ActorFilms . class );
角色预设 Java @Configuration
class Config {
@Bean
ChatClient chatClient ( ChatClient . Builder builder ) {
return builder . defaultSystem ( "You are a friendly chat bot that answers question in the voice of a Pirate" )
. build ();
}
}
在上面builder.defaultSystem()创建ChatClient的时候,还可以选择使用模板({参数名称}),有机会在每次调用前修改请求参数
Java @Configuration
public class ChatClientConfiguration {
@Bean
ChatClient chatClient ( ChatClient . Builder builder ) {
return builder . defaultSystem ( "请你给我回答问题时,前面带一个{word}" )
. build ();
}
}
测试代码如下:
Java @Autowired
private ChatClient dashScopeChatClient ;
@GetMapping ( "/chat" )
public String chat ( String message , String words ) {
return dashScopeChatClient . prompt ( message )
. system ( sp -> sp . param ( "word" , words )) // 传递参数
. call ()
. content ();
}
其他默认设置 除了defaultSystem之外,还可以在ChatClient.Builder上指定其他默认提示。
defaultOptions(ChatOptions chatOptions):传入ChatOptions类中定义的可移植选项或特定于模型实现的如DashScopeChatOptions选项。有关特定于模型的ChatOptions实现的更多信息,请参阅JavaDocs defaultFunction(String name, String description, java.util.function.Function<I, O> function):name用于在文本中引用该函数,description解释该函数的用途并帮助AI模型选择正确的函数以获得准确的响应,参数function是模型将在必要时执行的Java函数实例 defaultFunctions(String... functionNames):应用程序上下文中定义的java.util.Function的Bean名称 defaultUser(String text)、defaultUser(Resource text)、defaultUser(Consumer<UserSpec> userSpecConsumer)这些方法允许您定义用户消息输入,Consumer<UserSpec>允许您使用lambda指定用户消息输入和任何默认参数 defaultAdvisors(RequestResponseAdvisor... advisor):Advisors允许修改用于创建Prompt的数据,QuestionAnswerAdvisor实现通过在Prompt中附加与文本相关的上下文信息来实现Retrieval Augmented Generation模式 defaultAdvisors(Consumer<AdvisorSpec> advisorSpecConsumer):此方法允许您定义一个Consumer并使用AdvisorSpec配置多个Advisor,Advisor可以修改用于创建Prompt的最终数据,Consumer<AdvisorSpec>允许您指定lambda来添加Advisor例如QuestionAnswerAdvisor 可以在运行时使用ChatClient提供的不带default前缀的相应方法覆盖这些默认值:
options(ChatOptions chatOptions) function(String name, String description, java.util.function.Function<I, O> function) functions(String... functionNames) user(String text)、user(Resource text)、user(Consumer<UserSpec> userSpecConsumer) advisors(RequestResponseAdvisor... advisor) advisors(Consumer<AdvisorSpec> advisorSpecConsumer) 多模态支持 下使用阿里云百炼平台中提供的模型进行演示,也可以参考Spring AI Alibaba的官方示例
图像理解和图像生成 图像理解 图片生成
Java @GetMapping ( "/image" )
public String image ( String prompt ) throws Exception {
String url = "https://dashscope.oss-cn-beijing.aliyuncs.com/images/dog_and_girl.jpeg" ;
List < Media > mediaList = List . of ( new Media ( MimeTypeUtils . IMAGE_PNG , new URI ( url ). toURL (). toURI ()));
UserMessage message = UserMessage . builder (). text ( prompt ). media ( mediaList ). build ();
ChatResponse response = client
. prompt ( new Prompt ( message ))
. call ()
. chatResponse ();
return response . getResult (). getOutput (). getText ();
}
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 @Autowired
private DashScopeImageModel imageModel ;
@RequestMapping ( "/gen1" )
public void gen1 () {
// 使用默认模型:wanx-v1
ImageResponse imageResponse = imageModel . call ( new ImagePrompt ( "帮我生成一张小猫的图片" ));
System . out . println ( imageResponse . getResult (). getOutput (). getUrl ());
}
@RequestMapping ( "/gen2" )
public void gen2 () {
// 切换模型为wan2.2-t2i-flash
DashScopeImageOptions imageOptions = DashScopeImageOptions . builder ()
. model ( "wan2.2-t2i-flash" )
. build ();
ImageResponse imageResponse = imageModel . call ( new ImagePrompt ( "帮我生成一张小猫的图片" , imageOptions ));
System . out . println ( imageResponse . getResult (). getOutput (). getUrl ());
}
除了在代码中通过选项类设置模型以外,还可以在配置文件中进行配置,先在DashScopeImageAutoConfiguration找到配置类DashScopeImageProperties:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 @AutoConfiguration (
after = { RestClientAutoConfiguration . class , WebClientAutoConfiguration . class , SpringAiRetryAutoConfiguration . class }
)
@ConditionalOnClass ({ DashScopeImageApi . class })
@ConditionalOnDashScopeEnabled
@ConditionalOnProperty (
name = { "spring.ai.model.image" },
havingValue = "dashscope" ,
matchIfMissing = true
)
@EnableConfigurationProperties ({ DashScopeConnectionProperties . class , DashScopeImageProperties . class })
@ImportAutoConfiguration (
classes = { SpringAiRetryAutoConfiguration . class , RestClientAutoConfiguration . class , WebClientAutoConfiguration . class }
)
public class DashScopeImageAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DashScopeImageModel dashScopeImageModel ( DashScopeConnectionProperties commonProperties , DashScopeImageProperties imageProperties , ObjectProvider < RestClient . Builder > restClientBuilderProvider , RetryTemplate retryTemplate , ResponseErrorHandler responseErrorHandler , ObjectProvider < ObservationRegistry > observationRegistry , ObjectProvider < ImageModelObservationConvention > observationConvention ) {
ResolvedConnectionProperties resolved = DashScopeConnectionUtils . resolveConnectionProperties ( commonProperties , imageProperties , "image" );
DashScopeImageApi dashScopeImageApi = DashScopeImageApi . builder (). apiKey ( resolved . apiKey ()). baseUrl ( resolved . baseUrl ()). imagesPath ( imageProperties . getImagesPath ()). queryTaskPath ( imageProperties . getQueryTaskPath ()). workSpaceId ( resolved . workspaceId ()). restClientBuilder (( RestClient . Builder ) restClientBuilderProvider . getIfAvailable ( RestClient :: builder )). responseErrorHandler ( responseErrorHandler ). build ();
DashScopeImageModel dashScopeImageModel = DashScopeImageModel . builder (). dashScopeApi ( dashScopeImageApi ). defaultOptions ( imageProperties . getOptions ()). retryTemplate ( retryTemplate ). observationRegistry (( ObservationRegistry ) observationRegistry . getIfUnique (() -> ObservationRegistry . NOOP )). build ();
Objects . requireNonNull ( dashScopeImageModel );
observationConvention . ifAvailable ( dashScopeImageModel :: setObservationConvention );
return dashScopeImageModel ;
}
}
在DashScopeImageProperties就可以找到CONFIG_PREFIX字段,这个字段就代表前缀,然后进入DashScopeImageOptions找到需要配置的属性即可:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 @JsonInclude ( Include . NON_NULL )
public class DashScopeImageOptions implements ImageOptions {
@JsonProperty ( "model" )
private String model ;
@JsonProperty ( "n" )
private Integer n ;
@JsonProperty ( "width" )
private Integer width ;
@JsonProperty ( "height" )
private Integer height ;
@JsonProperty ( "size" )
private String size ;
@JsonProperty ( "style" )
private String style ;
@JsonProperty ( "seed" )
private Integer seed ;
@JsonProperty ( "ref_img" )
private String refImg ;
@JsonProperty ( "ref_strength" )
private Float refStrength ;
@JsonProperty ( "response_format" )
private String responseFormat ;
@JsonProperty ( "ref_mode" )
private String refMode ;
@JsonProperty ( "negative_prompt" )
private String negativePrompt ;
@JsonProperty ( "prompt_extend" )
private Boolean promptExtend ;
@JsonProperty ( "watermark" )
private Boolean watermark ;
@JsonProperty ( "function" )
private String function ;
@JsonProperty ( "base_image_url" )
private String baseImageUrl ;
@JsonProperty ( "mask_image_url" )
private String maskImageUrl ;
@JsonProperty ( "sketch_image_url" )
private String sketchImageUrl ;
@JsonProperty ( "sketch_weight" )
private Integer sketchWeight ;
@JsonProperty ( "sketch_extraction" )
private Boolean sketchExtraction ;
@JsonProperty ( "sketch_color" )
private Integer [][] sketchColor ;
@JsonProperty ( "mask_color" )
private Integer [][] maskColor ;
@JsonProperty ( "max_images" )
private Integer maxImages ;
@JsonProperty ( "enable_interleave" )
private Boolean enableInterleave ;
// ...
}
其中@JsonProperty中的值即为对应官方文档 的配置
需要注意的是,阿里云文生图的模型有很多,模型对应的请求参数也不同,需要在请求体中具体查看
语音合成和语音识别 语音合成 语音识别
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 @Autowired
private DashScopeAudioSpeechModel audioSpeechModel ;
private final String TEXT = """
今天是一个适合安静下来的下午,窗外的光线不算刺眼,房间里也没有太多杂音。我坐在桌前,打开电脑,准备测试一段中文 AI 语音朗读效果。
""" ;
@RequestMapping ( "/gen1" )
public void gen1 () throws IOException {
// 使用默认模型
TextToSpeechResponse speechResponse = audioSpeechModel . call ( new TextToSpeechPrompt ( TEXT ));
File file = new File ( System . getProperty ( "user.dir" ) + "/output.mp3" );
try ( FileOutputStream fos = new FileOutputStream ( file )) {
byte [] output = speechResponse . getResult (). getOutput ();
fos . write ( output );
}
catch ( IOException e ) {
throw new IOException ( e . getMessage ());
}
}
@RequestMapping ( "/gen2" )
public void gen2 () throws IOException {
// 使用模型cosyvoice-v3-flash,音色为猴哥longhouge_v3,文件格式为wav
DashScopeAudioSpeechOptions speechOptions = DashScopeAudioSpeechOptions . builder ()
. model ( "cosyvoice-v3-flash" )
. responseFormat ( DashScopeAudioSpeechApi . ResponseFormat . WAV )
. voice ( "longhouge_v3" )
. build ();
TextToSpeechResponse speechResponse = audioSpeechModel . call ( new TextToSpeechPrompt ( TEXT , speechOptions ));
File file = new File ( System . getProperty ( "user.dir" ) + "/output.wav" );
try ( FileOutputStream fos = new FileOutputStream ( file )) {
byte [] output = speechResponse . getResult (). getOutput ();
fos . write ( output );
}
catch ( IOException e ) {
throw new IOException ( e . getMessage ());
}
}
除了在代码中通过选项类设置模型以外,还可以在配置文件中进行配置,先在DashScopeAutoAutoConfiguration找到配置类DashScopeAudioSpeechSynthesisProperties:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 @AutoConfiguration (
after = { RestClientAutoConfiguration . class , WebClientAutoConfiguration . class , SpringAiRetryAutoConfiguration . class }
)
@ConditionalOnClass ({ DashScopeAudioSpeechApi . class })
@ConditionalOnDashScopeEnabled
@ConditionalOnProperty (
name = { "spring.ai.model.audio.speech" },
havingValue = "dashscope" ,
matchIfMissing = true
)
@EnableConfigurationProperties ({ DashScopeConnectionProperties . class , DashScopeAudioSpeechSynthesisProperties . class })
@ImportAutoConfiguration (
classes = { SpringAiRetryAutoConfiguration . class , RestClientAutoConfiguration . class , WebClientAutoConfiguration . class }
)
public class DashScopeAudioSpeechAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DashScopeAudioSpeechModel dashScopeSpeechSynthesisModel ( DashScopeConnectionProperties commonProperties , DashScopeAudioSpeechSynthesisProperties audioSpeechProperties , RetryTemplate retryTemplate ) {
DashScopeAudioSpeechApi dashScopeSpeechSynthesisApi = this . audioSpeechApi ( commonProperties , audioSpeechProperties );
return DashScopeAudioSpeechModel . builder (). audioSpeechApi ( dashScopeSpeechSynthesisApi ). defaultOptions ( audioSpeechProperties . getOptions ()). retryTemplate ( retryTemplate ). build ();
}
private DashScopeAudioSpeechApi audioSpeechApi ( DashScopeConnectionProperties commonProperties , DashScopeAudioSpeechSynthesisProperties audioSpeechProperties ) {
ResolvedConnectionProperties resolved = DashScopeConnectionUtils . resolveConnectionProperties ( commonProperties , audioSpeechProperties , "audio.synthesis" );
return new DashScopeAudioSpeechApi ( resolved . apiKey (), resolved . workspaceId ());
}
}
接着在DashScopeAudioSpeechSynthesisProperties找到CONFIG_PREFIX即为配置前缀,在DashScopeAudioSpeechOptions中可以查看支持配置的字段:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 @JsonInclude ( Include . NON_NULL )
public class DashScopeAudioSpeechOptions implements TextToSpeechOptions {
@JsonProperty ( "model" )
private String model ;
@JsonProperty ( "text" )
private String text ;
@JsonProperty ( "voice" )
private String voice ;
@JsonProperty ( "request_text_type" )
private DashScopeAudioSpeechApi . RequestTextType requestTextType ;
@JsonProperty ( "sample_rate" )
private Integer sampleRate ;
@JsonProperty ( "volume" )
private Integer volume ;
@JsonProperty ( "speed" )
private Double speed ;
@JsonProperty ( "pitch" )
private Double pitch ;
@JsonProperty ( "enable_word_timestamp" )
private Boolean enableWordTimestamp ;
@JsonProperty ( "enable_phoneme_timestamp" )
private Boolean enablePhonemeTimestamp ;
@JsonProperty ( "enable_ssml" )
private Boolean enableSsml ;
@JsonProperty ( "bit_rate" )
private Integer bitRate ;
@JsonProperty ( "seed" )
private Integer seed ;
@JsonProperty ( "language_hints" )
private List < String > languageHints ;
@JsonProperty ( "instruction" )
private String instruction ;
@JsonProperty ( "response_format" )
private DashScopeAudioSpeechApi . ResponseFormat responseFormat ;
// ...
}
其中@JsonProperty中的值即为对应官方文档 的配置
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 @Autowired
private DashScopeAudioTranscriptionModel transcriptionModel ;
private static final String PARAFORMER_TEST_AUDIO_URL = "https://dashscope.oss-cn-beijing.aliyuncs.com/samples/audio/paraformer/hello_world_female2.wav" ;
@RequestMapping ( "/gen3" )
public void gen3 () throws MalformedURLException {
// 使用URL资源
Resource audioFile = new UrlResource ( PARAFORMER_TEST_AUDIO_URL );
AudioTranscriptionPrompt prompt = new AudioTranscriptionPrompt ( audioFile );
AudioTranscriptionResponse response = transcriptionModel . call ( prompt );
System . out . println ( response . getResult (). getOutput ());
}
@RequestMapping ( "/gen4" )
public void gen4 () throws MalformedURLException {
DashScopeAudioTranscriptionOptions options = DashScopeAudioTranscriptionOptions . builder ()
. model ( "paraformer-v1" )
. format ( DashScopeAudioTranscriptionApi . AudioFormat . WAV )
. build ();
Resource audioFile = new UrlResource ( PARAFORMER_TEST_AUDIO_URL );
AudioTranscriptionPrompt prompt = new AudioTranscriptionPrompt ( audioFile , options );
AudioTranscriptionResponse response = transcriptionModel . call ( prompt );
System . out . println ( response . getResult (). getOutput ());
}
关于选项等的设置,参考上面介绍的思路,此处不再介绍,除此之外还可以参考官方文档
视频生成 Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 @Autowired
private DashScopeVideoModel dashScopeVideoModel ;
private final String PROMPT_TEXT = "一只橘猫坐在窗台上晒太阳,微风吹动窗帘,镜头缓慢推进,电影感" ;
@RequestMapping ( "/gen1" )
public void gen1 () {
DashScopeVideoOptions videoOptions = DashScopeVideoOptions . builder ()
. model ( "wan2.6-t2v" )
. input (
DashScopeVideoOptions . InputOptions . builder ()
. prompt ( PROMPT_TEXT )
. build ()
)
. parameters (
DashScopeVideoOptions . ParametersOptions . builder ()
. size ( "1280*720" )
. duration ( 5 )
. promptExtend ( true )
. seed ( 12345L )
. build ()
)
. build ();
VideoPrompt videoPrompt = new VideoPrompt (
List . of ( new VideoMessage ( PROMPT_TEXT )),
videoOptions
);
VideoResponse response = dashScopeVideoModel . call ( videoPrompt );
System . out . println ( response . getResult (). getOutput (). videoUrl ());;
}
关于选项等的设置,参考上面介绍的思路,此处不再介绍,除此之外还可以参考官方文档
介绍 随着大语言模型(LLM)能力的飞速发展,我们不再满足于仅仅让它们生成文本或回答问题。我们期望它们能成为真正的智能助手,能够与外部世界交互,执行具体任务,比如查询数据库、发送邮件或分析数据。为了解决这一挑战,工具调用(Tool Calling)应运而生
Tool Calling(工具调用),是AI应用程序中的一种常见模式,允许大语言模型(LLM)根据用户请求,智能地选择、调用外部工具(如函数、API、服务)并获取结果的技术流程,从而增强其功能
在早期更流行叫Function Calling(函数调用),其是指LLM请求调用一个开发者预定义的函数 (Function),这里的“函数”就是代码中的一个方法。而Tool Calling是一个更通用、更广泛的概念,不仅包含了Function Calling,还涵盖了调用其他类型的工具,所以后面发生了更名
文档与基础使用 工具调用可以参考官方文档 以及官方文档(翻译版) 。下面给出工具调用的示例
工具调用主要分为声明式和编程式,声明式就是通过注解修饰工具方法和对应的参数,编程式就是编写代码提供工具方法
声明式工具调用 声明式工具调用主要用到两个注解:
@Tool修饰方法,表示指定方法为一个工具方法 @ToolParam修饰方法的参数 具体介绍参考文档
使用@Tool时,需要注意,建议显式指定description 。例如下面的根据地区获取该地区的日期和时间工具方法示例:
Java @Component
public class DateTimeTool {
@Tool ( description = "根据地区获取该地区的日期和时间" )
public String getCurrentDateAndTime ( @ToolParam ( description = "地区名称" ) String place ) {
return switch ( place ) {
case "北京" -> "2026-04-02 19:00" ;
case "纽约" -> "2026-04-02 7:00" ;
default -> "错误地区" ;
};
}
}
接着,需要为AI添加工具,使用tools()方法:
Java @Autowired
private DateTimeTool dateTimeTool ;
@RequestMapping ( "/gen2" )
public String gen2 ( String message ) {
return chatClient . prompt ()
. user ( message )
. tools ( dateTimeTool )
. call (). content ();
}
对比有无工具时,AI的回复:
除了上面在运行时指定工具以外,还可以指定默认工具,使用defaultTools()方法即可,例如:
Java @Configuration
public class AiConfig {
@Bean
public ChatClient chatClient ( ChatClient . Builder builder ) {
return builder
. defaultTools ( new DateTimeTool ())
. build ();
}
}
但是需要注意的是,相同的工具方法只能定义一次 ,否则在调用时会报错,例如:
Text Only 2026-04-02T19:20:46.506+08:00 ERROR 48972 --- [spring-ai-tool-calling] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.IllegalStateException: Multiple tools with the same name (getCurrentDateAndTime) found in ToolCallingChatOptions] with root cause
编程式工具调用 对于编程式来说,需要做的工作会比声明式多一小部分,就是通过反射获取到工具方法并设置到模型的工具回调中,还是以上面的工具方法为例,去掉其@Tool和@ToolParam注解:
Java // 编程式
public String getCurrentDateAndTime ( String place ) {
return switch ( place ) {
case "北京" -> "2026-04-02 19:00" ;
case "纽约" -> "2026-04-02 7:00" ;
default -> "错误地区" ;
};
}
接着回到AI调用的方法中,为了能拿到方法,首先需要通过反射获取到方法对象:
Java import org.springframework.util.ReflectionUtils ;
Method getCurrentDateAndTimeMethod =
ReflectionUtils . findMethod ( DateTimeTool . class , "getCurrentDateAndTime" );
// 断言方法对象不能为空
Assert . notNull ( getCurrentDateAndTimeMethod , "指定方法不存在" );
然后该方法为工具方法:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 // 定义工具回调对象
ToolCallback toolCallback = MethodToolCallback . builder ()
. toolDefinition ( ToolDefinition . builder () // 定义工具信息
. name ( "getCurrentDateAndTime" ) // 工具方法名称
. description ( "根据地区获取该地区的日期和时间" ) // 工具方法描述
// 工具参数约束
. inputSchema ( """
{
"type": "object",
"properties": {
"place": {
"type": "string",
"description": "地区名称"
}
},
"required": ["place"]
}
""" )
. build ())
. toolMethod ( getCurrentDateAndTimeMethod ) // 注册为工具方法
. toolObject ( dateTimeTool ) // 非静态方法时需要工具类对象
. build ();
最后,同样在模型中添加工具方法回调:
Java chatClient . prompt ()
. user ( message )
. toolCallbacks ( toolCallback ) // 工具方法回调
. call (). content ();
需要注意的是,在官方文档的描述中,使用的是ToolDefinition.Builder,此时必须要工具方法指定name,否则会报错,如果是官方文档的代码示例ToolDefinitions.Builder(ToolDefinition有s),则因为builder()中可以携带方法对象,所以可以省略name,即:
Java ToolCallback toolCallback = MethodToolCallback . builder ()
. toolDefinition ( ToolDefinitions . builder ( getCurrentDateAndTimeMethod )
// ...
. build ())
// ...
如果使用的是ToolDefinitions,可以不手写Json Schema,默认会生成JSON Schema,此时只要方法有参数就是必传,如果想变成选传,可以在定义方法的参数中使用@Nullable,表示可以为空,例如:
Java public String getCurrentDateAndTime ( @Nullable String place ) {
// ...
}
工具上下文ToolContext 上面工具方法的参数都是由AI从用户的提示词中获取,但是有的时候部分数据从提示词无法获取,例如用户ID,此时可以在调用工具之前,将用户ID通过上下文传递给AI。例如下面的工具方法代码:
Java @Tool ( description = "获取当前用户信息" )
public String getCurrentUserData ( ToolContext toolContext ) {
return "当前用户的ID信息为:" + toolContext . getContext (). get ( "userId" );
}
在添加工具方法给AI时还需要给上下文设置信息:
Java @RequestMapping ( "/gen5" )
public String gen5 ( String message ) {
return chatClient . prompt ()
. user ( message )
. tools ( dateTimeTool )
. toolContext ( Map . of ( "userId" , "1" )) // 传递参数给上下文
. call (). content ();
}
可以得到类似下面的回复:
Text Only 根据系统返回的信息,当前用户的ID是 **1**。 这是系统识别到的用户标识信息。如果您需要了解更多关于该用户的详细信息,可能需要联系系统管理员或查看其他相关系统功能。
结果直接返回 有的时候,只需要AI自动调用工具并将结果直接返回给用户,而不需要将结果经过AI二次加工,例如一些结构化数据。在上面的示例中,默认情况下都是会将数据给AI进行二次加工
对于声明式来说,可以在@Tool注解中添加returnDirect,将其值设置为true即可将工具方法执行的结果直接返回给用户
对于编程式来说,需要创建ToolMetaData对象,在其中设置returnDirect
例如下面的代码:
工具调用流程介绍 模型上下文协议(MCP) 介绍 MCP是“让AI应用接入外部世界的标准协议”,工具调用是其中一种非常重要的能力,但两者不是一回事
MCP全称Model Context Protocol。官方把它定义为一种开放标准,用来把AI应用连接到外部系统,比如本地文件、数据库、搜索、业务API、工作流等。它的核心不是某一个模型能力,而是一套“应用和外部能力如何对接”的统一协议。可以把它理解成:MCP = AI世界里的USB-C,不是某个具体设备,而是“统一插口标准”。
从架构上看,MCP是host -> client -> server模式:
host是ChatGPT、Claude、IDE这类AI应用 client负责和某个MCP server建立连接 server暴露能力,比如tools、resources、prompts Java MCP架构 参考官方文档
Spring AI应用使用MCP Spring AI应用使用MCP,本质就是Spring AI应用作为MCP Client,首先引入下面的依赖:
XML <dependency>
<groupId> org.springframework.ai</groupId>
<artifactId> spring-ai-starter-mcp-client</artifactId>
<version> 1.1.2</version>
</dependency>
接着,编写配置文件内容:
YAML spring :
ai :
mcp :
client :
stdio :
servers-configuration : classpath:/mcp/mcp-servers.json # 存放MCP Json配置的文件
接着,在项目的resources目录下创建一个mcp目录,并在该目录中新增mcp-servers.json文件,以chrome dev tools mcp为例:
JSON {
"mcpServers" : {
"chrome-devtools" : {
"command" : "npx" ,
"args" : [ "-y" , "chrome-devtools-mcp@latest" ]
}
}
}
Note
除了上面的方式配置MCP以外,还可以在配置文件中直接编写MCP需要使用到的命令、配置等,具体参考官方文档
需要注意的是,在Windows下,像npx、npm这些命令本质都需要在cmd中执行,而不是原生的可执行文件,所以Java的ProcessBuilder无法直接执行,所以上面的MCP是无法被Java程序正常执行的,有两种执行方式:
接着,为AI添加工具:
Java @Configuration
public class AiConfig {
@Bean
public ChatClient chatClient ( ChatClient . Builder builder ,
ToolCallbackProvider toolCallbackProvider ) {
return builder
. defaultToolCallbacks ( toolCallbackProvider )
. build ();
}
}
使用Java创建MCP工具 MCP客户端和服务端通信方式有两种:
stdio:适用于本地进程通信。客户端启动MCP服务端子进程,并通过stdin/stdout交换JSON-RPC消息,优点是简单、高效、无网络开销 sse:适用于远程通信。客户端通过HTTP与MCP服务端交互,支持流式返回,也可结合SSE实现服务端消息推送,是当前MCP官方规范推荐的远程传输方式 下面根据这两种通信方式进行MCP工具创建,首先引入MCP Server的依赖
对于stdio引入下面的依赖:
XML <dependency>
<groupId> org.springframework.ai</groupId>
<artifactId> spring-ai-starter-mcp-server</artifactId>
<version> 1.1.2</version>
</dependency>
对于sse引入下面的依赖:
XML <dependency>
<groupId> org.springframework.ai</groupId>
<artifactId> spring-ai-starter-mcp-server-webmvc</artifactId>
<version> 1.1.2</version>
</dependency>
sse其他依赖 sse除了上面的依赖,还可以使用下面的依赖:
XML <dependency>
<groupId> org.springframework.ai</groupId>
<artifactId> spring-ai-starter-mcp-server-webflux</artifactId>
<version> 1.1.2</version>
</dependency>
但是就会导致MCP端点(例如/sse)无法正常工作,这是因为项目中同时存在org.springframework.web.servlet.DispatcherServlet和org.springframework.web.reactive.DispatcherHandler,Spring Boot默认优先考虑前者
可以通过设置spring.main.web-application-type=reactive来解决:
YAML spring :
ai :
# ...
main :
web-application-type : reactive
接着编写配置
对于stdio来说,配置如下:
YAML spring :
ai :
mcp :
server :
name : user-info
version : 0.0.1
main :
web-application-type : none
banner-mode : off
对于sse来说,配置如下:
YAML server :
port : 8088 # 任意端口
spring :
ai :
mcp :
server :
name : user-info
version : 0.0.1
定义一个工具,为了演示,本次定义一个获取用户信息的工具,数据自行Mock:
用户信息实体 获取用户信息工具
Java @Data
@AllArgsConstructor
public class UserInfo {
private String name ;
private int age ;
private String sex ;
private String address ;
}
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 @Service
public class UserService {
static Map < String , UserInfo > map = new HashMap < String , UserInfo > ();
static {
map . put ( "zhangsan" , new UserInfo ( "zhangsan" , 18 , "男" , "北京" ));
map . put ( "lisi" , new UserInfo ( "lisi" , 19 , "男" , "上海" ));
map . put ( "wangwu" , new UserInfo ( "wangwu" , 20 , "女" , "广州" ));
map . put ( "zhaoliu" , new UserInfo ( "zhaoliu" , 21 , "女" , "深圳" ));
map . put ( "sunqi" , new UserInfo ( "sunqi" , 22 , "男" , "西安" ));
map . put ( "zhouba" , new UserInfo ( "zhouba" , 23 , "女" , "天津" ));
}
@Tool ( description = "根据用户姓名查找用户信息" )
public String getUserInfo ( String username ) {
if ( map . containsKey ( username )) {
return map . get ( username ). toString ();
}
return "未获取到用户信息" ;
}
}
最后,对于stdio的来说,暴露工具并打包:
Java @Configuration
public class ToolConfig {
@Bean
public ToolCallbackProvider getUserInfo ( UserService userService ){
return MethodToolCallbackProvider . builder ()
. toolObjects ( userService )
. build ();
}
}
打包完成后,编写MCP JSON供其他编程工具或者AI应用使用:
JSON 1
2
3
4
5
6
7
8
9
10
11
12
13 {
"mcpServers" : {
"user-info" : {
"command" : "java" ,
"args" : [
"-Dspring.ai.mcp.server.stdio=true" ,
"-Dlogging.pattern.console=" ,
"-jar" ,
"D:\\xxx" // jar包所在路径
]
}
}
}
对于sse,首先访问http://127.0.0.1:8088/sse,确保sse端点可用,接着编写MCP JSON配置:
JSON {
"mcpServers" : {
"user-info" : {
"url" : "http://127.0.0.1:8088/sse"
}
}
}
对于Spring AI应用,配置如下:
YAML spring :
ai :
mcp :
client :
sse :
connections :
user-info :
url : http://127.0.0.1:8088/sse
MCP推荐 参考常见MCP工具配置