SpringAIAlibaba之基础功能和基础类源码解析(2)
一、序言
SpringAIAlibaba系列的上一篇文章已经对SpringAIAlibaba做了一个入门,这里的入门就是知道SpringAIAlibaba是做什么的,然后也可以调通通义模型,本篇文章就再做一个入门PLUS吧,可以使用一些基础的功能,了解一些核心类实现。
二、基础功能
使用 PromptTemplate 动态构建提示词可以让你的应用更灵活和可配置。以下是完整的实现方案:
1. YAML 配置文件
创建 src/main/resources/application.yml:
spring:ai:dashscope:api-key: ${DASHSCOPE_API_KEY:your-api-key}# 提示词模板配置
prompt:templates:chat:system: "你是一个专业的{role},擅长{expertise}。请用{style}的方式回答问题。"user: "用户问题:{question}\n背景信息:{context}\n请求类型:{requestType}"translation:system: "你是一个专业翻译,负责将{sourceLanguage}翻译成{targetLanguage}。"user: "请翻译以下内容:{content}"summary:system: "你是一个文档总结专家,能够提取关键信息并生成简洁的摘要。"user: "请总结以下内容,控制在{maxWords}字以内:\n{document}"# 默认参数
prompt:defaults:role: "AI助手"expertise: "回答各种问题"style: "友好专业"maxWords: "200"
2. 配置类
package com.example.yangai.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.Map;@Component
@ConfigurationProperties(prefix = "prompt")
public class PromptConfig {private Map<String, Map<String, String>> templates;private Map<String, String> defaults;// getters and setterspublic Map<String, Map<String, String>> getTemplates() {return templates;}public void setTemplates(Map<String, Map<String, String>> templates) {this.templates = templates;}public Map<String, String> getDefaults() {return defaults;}public void setDefaults(Map<String, String> defaults) {this.defaults = defaults;}
}
3. 提示词服务类
package com.example.yangai.service;import com.example.yangai.config.PromptConfig;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Service
public class PromptService {private final PromptConfig promptConfig;public PromptService(PromptConfig promptConfig) {this.promptConfig = promptConfig;}/*** 构建聊天提示词*/public Prompt buildChatPrompt(Map<String, Object> variables) {return buildPrompt("chat", variables);}/*** 构建翻译提示词*/public Prompt buildTranslationPrompt(String sourceLanguage, String targetLanguage, String content) {Map<String, Object> variables = Map.of("sourceLanguage", sourceLanguage,"targetLanguage", targetLanguage,"content", content);return buildPrompt("translation", variables);}/*** 构建摘要提示词*/public Prompt buildSummaryPrompt(String document, Integer maxWords) {Map<String, Object> variables = Map.of("document", document,"maxWords", maxWords != null ? maxWords.toString() : promptConfig.getDefaults().get("maxWords"));return buildPrompt("summary", variables);}/*** 通用提示词构建方法*/public Prompt buildPrompt(String templateName, Map<String, Object> variables) {Map<String, String> template = promptConfig.getTemplates().get(templateName);if (template == null) {throw new IllegalArgumentException("Template not found: " + templateName);}// 合并默认参数和用户参数Map<String, Object> mergedVariables = new HashMap<>(promptConfig.getDefaults());mergedVariables.putAll(variables);// 构建系统消息SystemMessage systemMessage = null;if (template.containsKey("system")) {PromptTemplate systemTemplate = new PromptTemplate(template.get("system"));systemMessage = systemTemplate.createMessage(mergedVariables);}// 构建用户消息UserMessage userMessage = null;if (template.containsKey("user")) {PromptTemplate userTemplate = new PromptTemplate(template.get("user"));userMessage = userTemplate.createMessage(mergedVariables);}// 组合消息if (systemMessage != null && userMessage != null) {return new Prompt(List.of(systemMessage, userMessage));} else if (userMessage != null) {return new Prompt(userMessage);} else if (systemMessage != null) {return new Prompt(systemMessage);} else {throw new IllegalArgumentException("No valid template found");}}
}
4. 更新的 ChatModel 实现
package com.example.yangai.service;import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.aigc.generation.models.QwenParam;
import com.alibaba.dashscope.common.Message;
import org.springframework.ai.chat.ChatClient;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Service
public class TongyiChatModel implements ChatClient {@Value("${spring.ai.dashscope.api-key}")private String apiKey;@Overridepublic ChatResponse call(Prompt prompt) {try {Generation gen = new Generation();// 转换消息格式List<Message> messages = prompt.getInstructions().stream().map(instruction -> Message.builder().role(mapRole(instruction.getMessageType().name())).content(instruction.getContent()).build()).collect(Collectors.toList());// 构建请求参数GenerationParam param = GenerationParam.builder().apiKey(apiKey).model(QwenParam.Model.QWEN_TURBO).messages(messages).resultFormat(GenerationParam.ResultFormat.MESSAGE).build();// 调用模型GenerationResult result = gen.call(param);String content = result.getOutput().getChoices().get(0).getMessage().getContent();// 转换为 Spring AI 格式org.springframework.ai.chat.Generation springGeneration = new org.springframework.ai.chat.Generation(content);return new ChatResponse(Arrays.asList(springGeneration));} catch (Exception e) {throw new RuntimeException("调用通义千问失败", e);}}private String mapRole(String messageType) {switch (messageType.toLowerCase()) {case "system": return "system";case "user": return "user";case "assistant": return "assistant";default: return "user";}}
}
5. 控制器使用示例
package com.example.yangai.controller;import com.example.yangai.service.PromptService;
import com.example.yangai.service.TongyiChatModel;
import org.springframework.ai.chat.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController
@RequestMapping("/api/chat")
public class ChatController {private final PromptService promptService;private final TongyiChatModel chatModel;public ChatController(PromptService promptService, TongyiChatModel chatModel) {this.promptService = promptService;this.chatModel = chatModel;}/*** 通用聊天接口*/@PostMapping("/ask")public ChatResponse ask(@RequestBody Map<String, Object> request) {String question = (String) request.get("question");String context = (String) request.getOrDefault("context", "");String requestType = (String) request.getOrDefault("requestType", "一般询问");Map<String, Object> variables = Map.of("question", question,"context", context,"requestType", requestType,"role", request.getOrDefault("role", "AI助手"),"expertise", request.getOrDefault("expertise", "回答各种问题"),"style", request.getOrDefault("style", "友好专业"));Prompt prompt = promptService.buildChatPrompt(variables);return chatModel.call(prompt);}/*** 翻译接口*/@PostMapping("/translate")public ChatResponse translate(@RequestBody Map<String, String> request) {String sourceLanguage = request.get("sourceLanguage");String targetLanguage = request.get("targetLanguage");String content = request.get("content");Prompt prompt = promptService.buildTranslationPrompt(sourceLanguage, targetLanguage, content);return chatModel.call(prompt);}/*** 摘要接口*/@PostMapping("/summarize")public ChatResponse summarize(@RequestBody Map<String, Object> request) {String document = (String) request.get("document");Integer maxWords = (Integer) request.get("maxWords");Prompt prompt = promptService.buildSummaryPrompt(document, maxWords);return chatModel.call(prompt);}
}
6. 使用示例
聊天请求:
curl -X POST http://localhost:8088/api/chat/ask \-H "Content-Type: application/json" \-d '{"question": "如何学习Spring Boot?","context": "我是Java初学者","role": "编程导师","expertise": "Java和Spring框架","style": "循序渐进"}'
翻译请求:
curl -X POST http://localhost:8088/api/chat/translate \-H "Content-Type: application/json" \-d '{"sourceLanguage": "中文","targetLanguage": "英文","content": "你好,世界!"}'
三、基础类解析
一次通信过程中涉及到的核心类的关系如下:
ChatResponse (最外层响应)
├── List<Generation> (多个候选回答)
│ ├── Generation (单个回答)
│ │ ├── AssistantMessage (消息内容)
│ │ └── GenerationMetadata (生成元数据)
│ └── ...
└── ChatResponseMetadata (响应级元数据)├── Usage (token使用统计)├── RateLimit (速率限制)└── 扩展字段
其中最外层的类就是SpringAI的,最内层的类就是SpringAIAlibaba,这里也能清楚的看出来SpringAI是对所有的模型接入做了一层抽象,底层可以对接多种模型的api,SpringAIAlibaba就是对通义模型的api实现,所以做了一些适配转换逻辑。
四、总结
本文通过一个具体的实践案例再次加深了对SpringAIAlibaba的使用,同时通过这个开发的过程也了解了过程中涉及到的核心响应类,以及基础关系,当然请求类也是相同的关系逻辑,后续继续介绍核心源码,以及框架的设计架构。
欢迎关注、一起交流、一起进步。