试用网站cms,廊坊哪里做网站好,河南第二建设集团有限公司网站,全国企业信用信息公开官网1. 背景在这里#xff0c;我主要分享的是在应用层面大模型相关的技术#xff0c;假如你已有一个现成的大模型接口#xff0c;无论是符合OpenAI规范的#xff0c;还是各家公司一些自己的接口#xff0c;例如Gemini#xff0c;Deepseek#xff0c;通义千问#xff0c;问心…1. 背景在这里我主要分享的是在应用层面大模型相关的技术假如你已有一个现成的大模型接口无论是符合OpenAI规范的还是各家公司一些自己的接口例如GeminiDeepseek通义千问问心一言等让用这些大模型来构建一些应用可以选取下面的方案使用低代码大模型应用搭建平台例如CozeDify FastGPT等这些平台带了流程编排知识库也很方便的和各种大模型对接向量模型、向量库有些还有了监控界面或者插件市场等能满足我们的大部分需求使用编程的方式来构建应用这种可以使用公司现有的技术栈提供更为灵活的使用接入现有的系统等或者从更高层面来说定制自己的大模型应用规范定制大模型应用构建平台接入平台等也可以把上面所说的低代码平台看作为自建大模型应用体系的一部分即可以通过代码的方式灵活去构建应用也通过平台更高效的去构建应用我们后面讲的主要是使用第二种方式我们选取一些现有的框架来实现Python主要Langchain相关技术Java也有一个对应的框架Langchain4j也有SpringAI这些框架帮我们做了很多事情封装一个通用的模型调用接口屏蔽了底层不同公司大模型接口的差异管理了会话和上下文会话就是将用户之前问的问题和现在问的问题关联起来结构化输出将大模型的文本输出转为程序可以使用的结构化对象例如JSON对象工具/函数调用可观测性模型效果评估一个框架LiteLLM专门把不同大模型接口适配为OpenAI格式的这也是一种屏蔽差异的方式2. 架构.png)整体架构如上下面主要介绍部分实现模型框架层SpringAIJava大模型应用开发框架模型推理能力Ollama利用本地CPU或GPU和现有训练模型实现模型推理能力便于开发生产环境需要替换监控追踪LangFuse追踪大模型应用请求例如输入输出耗时、Token消耗等3. 实现3.1 安装Ollama和LangFuse参照文档安装和启动SpringAI通过pom导入除此之外还导入了springboot ollama、opentelemetry等相关包后者是为了接入LangFuseproject xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.jd.jt/groupIdartifactIdai-base/artifactIdversion1.0-SNAPSHOT/versionnameArchetype - ai-base/nameurlhttp://maven.apache.org/urlparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.4.3/versionrelativePath/ !-- lookup parent from repository --/parentpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncodingproject.reporting.outputEncodingUTF-8/project.reporting.outputEncodingmaven.compiler.source21/maven.compiler.sourcemaven.compiler.target21/maven.compiler.target/propertiesdependencyManagementdependenciesdependencygroupIdio.opentelemetry.instrumentation/groupIdartifactIdopentelemetry-instrumentation-bom/artifactIdversion2.17.0/versiontypepom/typescopeimport/scope/dependencydependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-bom/artifactIdversion1.0.1/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter/artifactId/dependencydependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-model-ollama/artifactId/dependency!-- Spring AI needs a reactive web server to run for some reason--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdio.opentelemetry.instrumentation/groupIdartifactIdopentelemetry-spring-boot-starter/artifactId/dependency!-- Spring Boot Actuator for observability support --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!-- Micrometer Observation - OpenTelemetry bridge --dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-tracing-bridge-otel/artifactId/dependency!-- OpenTelemetry OTLP exporter for traces --dependencygroupIdio.opentelemetry/groupIdartifactIdopentelemetry-exporter-otlp/artifactId/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion1.18.38/version/dependency/dependencies/project3.2 起步示例import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import reactor.core.publisher.Flux;RestControllerSlf4jclass MyController {private final ChatClient chatClient;public MyController(ChatClient.Builder chatClientBuilder) {this.chatClient chatClientBuilder.build();}GetMapping(value /ai-stream, produces MediaType.TEXT_EVENT_STREAM_VALUE)FluxString generationStream(RequestParam(userInput) String userInput) {return this.chatClient.prompt().user(userInput).stream().content();}GetMapping(/ai)String generation(String userInput) {return this.chatClient.prompt().user(userInput).call().content();}application.propertiesspring.ai.ollama.chat.enabledtruespring.ai.model.chatollamaspring.ai.ollama.chat.options.modelqwen3:8bspring.ai.chat.observations.include-prompttruespring.ai.chat.observations.include-completiontruemanagement.tracing.sampling.probability1.0执行命令curl --location http://localhost:8080/ai?userInput%E4%BD%A0%E5%A5%BDthink嗯用户发来“你好”我需要以自然的方式回应。首先应该用中文回复保持友好和亲切的语气。可以简单问候比如“你好有什么我可以帮助你的吗”这样既回应了对方的问候又主动询问是否需要帮助符合我的设计原则。同时要避免使用复杂或生硬的表达让对话显得轻松。另外考虑到用户可能有各种需求保持开放式的提问可以引导他们进一步说明具体问题这样我才能更好地提供帮助。确保回复简洁明了符合日常交流的习惯。/think你好有什么我可以帮助你的吗3.3 提示词3.3.1 提示词模板将变量嵌入提示词模板中动态生成提示词public String generateChildName() {PromptTemplate promptTemplate new PromptTemplate(男方姓{maleName}女方姓{femaleName}帮孩子起名);Prompt prompt promptTemplate.create(Map.of(maleName, 宋, femaleName, 刘));return chatClient.prompt(prompt).call().content();}执行命令curl --location http://localhost:8080/get-namethink嗯用户让我帮忙给孩子起名男方姓宋女方姓刘。首先我需要了解用户的需求。他们可能想要一个结合双方姓氏的名字或者更倾向于其中一个姓氏。不过通常在中国孩子跟父姓所以可能主要用宋姓但有时候也会考虑双姓或者结合双方姓氏的元素。...提示词模板也可以通过Resource的方式使用这样提示词可以存在任何地方了工程文件、数据库、配置中心等把提示词模板放在外部更容易管理和实时变更我们也可以通过第三方库来实现这个功能3.3.2 提示词管理提示词管理是将提示词单独存储在工程外部提供一些易用的功能例如版本控制搜索提示词等。我们这用了LangFuse所以只要把它接入我们的项目即可在界面上面创建提示词模板并发布在Java项目中接入因为LangFuse没有Java的SDK所以我们需要使用Api的方式dependencygroupIdcom.github.lianjiatech/groupIdartifactIdretrofit-spring-boot-starter/artifactIdversion3.2.0/version/dependency 创建LangFuseClient Http请求Client改造之前写generateChildName方法从LangFuse获取提示词RetrofitClient(baseUrl http://localhost:3000)public interface LangFuseClient {GET(api/public/v2/prompts/{promptName})Headers(Authorization: Basic cGstbGYtNDFmZjZkYjItZWZjNC00YTg4LTkyNmItZmMxZDE1ZGUwNGNiOnNrLWxmLTYzN2RmMDE0LWVkZDItNDdhNi1iNmUwLTE0N2U2MjMyOWYyMQ)LFPrompt getPrompts(Path(promptName) String promptName);}public String generateChildName() {LFPrompt lfPrompt langFuseClient.getPrompts(起名);PromptTemplate promptTemplate new PromptTemplate(lfPrompt.getPrompt());Prompt prompt promptTemplate.create(Map.of(maleName, 宋, femaleName, 刘));return chatClient.prompt(prompt).call().content();}执行命令查看结果curl --location http://localhost:8080/get-namethink嗯用户让我帮忙给孩子起名男方姓宋女方姓刘。首先我需要确认用户的需求。他们可能希望名字中包含双方的姓氏或者结合两者的元素。不过用户没有明确说明是否要双姓所以可能需要考虑不同的可能性。接下来我得考虑名字的寓意和音韵。中文名字通常讲究平仄搭配读起来顺口同时要有好的寓意。比如宋和刘的组合可能需要找一个字来连接或者用两个字分别代表双方的姓氏。然后用户要求输出在30个字以内所以每个名字要简洁。可能需要列出多个选项让用户有选择的余地。同时要注意避免生僻字确保名字的易读性和美观性。3.3.3 提示词工程提示词工程是通过关注提示词开发和优化提升大语言模型处理复杂任务场景的能力具体可以参考提示工程指南主要涉及了提示词的基本结构例如包含指令、上下文用户输入输出指示通用技巧提示词技术零样本提示少样本提示链式思考如果有现成的提示词可以直接拿过来用开源的提示词awesome-chatgpt-prompts商业的提示词promptbase这样的网站很多甚至可以使用大模型来生成提示词通常会比自己从头开始写效率会高3.4 结构化输出将大模型的文本输出转化为固定结构public ListString generateChildName() {LFPrompt lfPrompt langFuseClient.getPrompts(起名);PromptTemplate promptTemplate new PromptTemplate(lfPrompt.getPrompt());Prompt prompt promptTemplate.create(Map.of(maleName, 宋, femaleName, 刘));return chatClient.prompt(prompt).call().entity(new ListOutputConverter(new DefaultConversionService()));}执行命令可以看到返回结果变为了数组格式但并不完美通过提示词关闭think内容后格式上面还是有一些问题后续SpringAI会支持通过参数的方式关闭think过程curl --location http://localhost:8080/get-name[think\n\n/think\n\n宋明轩,宋子涵,宋浩然,宋梓睿,宋俊熙,宋天宇,宋浩宇,宋梓豪,...]可以从提示词中看到Spring AI为我们加了约束输出的提示词输出结果也是按照这个格式这个截图是LangFuse的监控的一部分可以看到请求大模型的记录另外可以将结果转为Map或者Java的某个具体Class3.5 会话和上下文在使用大模型应用的过程中通常会有两种模式单轮对话也就是一问一答之前问过的问题不会再考虑多轮对话在整个对话周期呢大模型会关联之前的问题综合之后给出回复要实现多轮对话的功能就需要我们记录是否是一次会话和会话期间的上下文因为大模型本身是无状态的它自己不记录这些内容import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.ai.chat.prompt.Prompt;import org.springframework.ai.chat.prompt.PromptTemplate;import org.springframework.ai.converter.ListOutputConverter;import org.springframework.core.convert.support.DefaultConversionService;import org.springframework.stereotype.Service;import java.util.List;import java.util.Map;Servicepublic class ChatService {private final ChatClient chatClient;public ChatService(ChatClient.Builder chatClientBuilder) {ChatMemory chatMemory MessageWindowChatMemory.builder().maxMessages(10).build();this.chatClient chatClientBuilder.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build()).build();}public ListString generateChildName(String conversationId, String surname, String gender) {// 系统提示词String systemMessage 你是一个专业的起名专家擅长根据中国传统文化和现代审美为孩子起名。请确保名字寓意美好、朗朗上口。;// 用户提示词模板String userMessageTemplate 请为姓{surname}的{gender}孩子起名要求1. 输出不超过10个名字2. 每个名字都要有寓意说明3. 名字要符合现代审美;PromptTemplate userPromptTemplate new PromptTemplate(userMessageTemplate);Prompt userPrompt userPromptTemplate.create(Map.of(surname, surname,gender, gender));return chatClient.prompt().system(systemMessage) // 系统提示词.user(userPrompt.getContents()) // 用户提示词.advisors(a - a.param(ChatMemory.CONVERSATION_ID, conversationId)).call().entity(new ListOutputConverter(new DefaultConversionService()));}}当发了两次请求之后从回复的内容可以看看大模型知道问了两次[think\n好的用户之前让我为姓宋的男孩起名现在又发了一个类似的请求但这次要求输出格式是逗号分隔的列表没有其他文本。我需要仔细分析用户的需求。\n\n首先用户可能是在测试我是否能按照新的格式要求输出或者他们需要将名字直接用于某个系统比如注册表单或应用程序所以需要简洁的格式。之前的回复是中文名字和寓意现在需要转换为只列出名字用逗号分隔。\n\n接下来我需要确保每个名字都符合现代审美同时保持寓意美好。用户之前提供的例子中有“宋子墨”、“宋知远”等这些名字都比较文雅符合传统文化同时又不失现代感。我需要保持这种风格避免生僻字确保名字朗朗上口。\n\n另外用户可能希望名字有独特的寓意同时避免重复。比如“宋明轩”中的“明”象征光明“轩”有气度的意思这样的组合既传统又现代。我需要检查每个名字的寓意是否明确并且没有重复的字。\n\n还要注意名字的结构姓氏“宋”是单姓名字通常为两个字所以每个名字都是两个字的组合。需要确保每个名字都符合这个结构并且整体看起来协调。\n\n最后用户要求不超过10个名字所以我要控制数量确保每个名字都经过筛选符合所有要求。同时输出格式必须严格遵循逗号分隔没有其他文字这可能需要在生成时特别注意格式的正确性避免任何多余的字符或空格。\n/think\n\n宋知远,宋云舟,宋景行,宋清晏,宋修远,宋墨言,宋怀瑾,宋承泽,宋明轩,宋致远]3.6 工具/函数调用利用外部的工具函数现有的能力同时利用大模型的能力结合起来完成复杂的任务例如某些数学计算函数天气查询服务下单服务订票服务发邮件等利用这些服务可以扩展大模型的能力又例如通过数据库查询信息文档库查询文档工具类import org.springframework.ai.tool.annotation.Tool;public class TicketUtil {Tool(description 输入出发地和目的地购买火车票)public static void buyTicket(String source, String target) {System.out.println(已购买 source 到 target 的票);}}大模型请求时使用tool方法import com.jd.ai.util.TicketUtil;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;Servicepublic class TicketService {private final ChatClient chatClient;public TicketService(ChatClient.Builder chatClientBuilder) {this.chatClient chatClientBuilder.build();}public String buyTicket() {return chatClient.prompt(帮我买北京到晋城的车票).tools(new TicketUtil()).call().content();}}大模型回复好的用户让我帮忙买北京到晋城的车票。首先我需要确认出发地和目的地是否正确。用户已经明确说明是北京到晋城所以直接调用buyTicket函数参数是source:北京target:晋城。没有其他参数需要处理比如日期或座位类型所以直接生成工具调用。然后系统返回“Done”说明操作成功。接下来我应该告诉用户购票成功并询问是否需要进一步帮助比如确认车次或办理乘车证。这样既完成了购票又提供了后续支持确保用户满意。 您的北京至晋城车票已成功购买请问是否需要帮您查询具体车次信息或办理乘车证相关手续呢控制台输出已购买北京到晋城的票3.7 知识库和RAGRAG(Retrieval Augmented Generation)是检索增强生成简单来讲通过提前检索一些知识将这些知识和用户的问题一起给大模型会提高大模型回复的质量3.7.1 知识库对于通过分词的检索系统来讲知识库一般是通过语义来检索的要实现语义检索需要对数据做向量化所以需要向量模型和向量库在这里我们用SimpleVectorStore来做向量库这是一个内存库import org.springframework.ai.embedding.EmbeddingModel;import org.springframework.ai.vectorstore.SimpleVectorStore;import org.springframework.ai.vectorstore.VectorStore;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;Configurationpublic class VectorStoreConfiguration {Beanpublic VectorStore vectorStore(EmbeddingModel embeddingModel) {return SimpleVectorStore.builder(embeddingModel).build();}}使用mxbai-embed-large作为向量模型这个模型也使用了ollama的功能且在SpringAI中不需要额外的配置就可以使用当然也可以参照文档做一些定制配置这里我们存了一些豆瓣的电影信息到里面import jakarta.annotation.PostConstruct;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.ai.document.Document;import org.springframework.ai.reader.JsonReader;import org.springframework.ai.vectorstore.VectorStore;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.io.FileSystemResource;import org.springframework.stereotype.Service;import java.util.List;Servicepublic class MovieService {private final ChatClient chatClient;AutowiredVectorStore vectorStore;PostConstructvoid load() {String sourceFile /Users/songjiyang.3/Downloads/less_douban_movie.json;JsonReader jsonReader new JsonReader(new FileSystemResource(sourceFile),title, year, id, role_desc, original_title);ListDocument documents jsonReader.get();this.vectorStore.add(d