JavaAi+DeepSeek 部署笔记

当前最热门的ai话题估计都是围绕着deepseek展开的。我也趁着这波热度来学习并记录一下。

参考资料:
文档地址
视频学习地址
课程仓库
代码仓库

目前主要有无代码平台:Dify 和Coze
javaAi的编码平台有:SpringAi 和 LangChain4j

当前模型可选择:OpenAi、阿里百炼、DeepSeek、智谱清言、硅基流动、Ollma(本地ai容器)

开源库:LangChain4j 。 使用它可接入各大模型,便于java开发者对大模型的api调用与集成。
使用阿里百炼平台进行测试 链接
使用硅基流动链接
新建一个sprintboot项目,采用maven管理依赖

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
</dependency>

<!-- 加载bom 后,所有langchain4j引用不需要加版本号 -->
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>0.36.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

yaml 配置文件:

langchain4j:
  open-ai:
    chat-model:
      # 课程测试 KEY,需要更换为实际可用 KEY  
      api-key: sk-xx
      model-name: qwen-turbo
      # 百炼兼容OpenAI接口规范,base_url为https://dashscope.aliyuncs.com/compatible-mode/v1
      base-url: https://dashscope.aliyuncs.com/compatible-mode/v1

本人进行复制操作时遇到的问题:在拷贝yaml配置文件的时候漏了 langchain4j: 节点,导致spring注入依赖没有办法匹配到配置文件,因此报错:


    @Bean
    @ConditionalOnProperty({"langchain4j.open-ai.chat-model.api-key"})
    OpenAiChatModel openAiChatModel(Properties properties) {
        ChatModelProperties chatModelProperties = properties.getChatModel();
        return OpenAiChatModel.builder().baseUrl(chatModelProperties.getBaseUrl()).apiKey(chatModelProperties.getApiKey()).organizationId(chatModelProperties.getOrganizationId()).modelName(chatModelProperties.getModelName()).temperature(chatModelProperties.getTemperature()).topP(chatModelProperties.getTopP()).stop(chatModelProperties.getStop()).maxTokens(chatModelProperties.getMaxTokens()).maxCompletionTokens(chatModelProperties.getMaxCompletionTokens()).presencePenalty(chatModelProperties.getPresencePenalty()).frequencyPenalty(chatModelProperties.getFrequencyPenalty()).logitBias(chatModelProperties.getLogitBias()).responseFormat(chatModelProperties.getResponseFormat()).strictJsonSchema(chatModelProperties.getStrictJsonSchema()).seed(chatModelProperties.getSeed()).user(chatModelProperties.getUser()).strictTools(chatModelProperties.getStrictTools()).parallelToolCalls(chatModelProperties.getParallelToolCalls()).timeout(chatModelProperties.getTimeout()).maxRetries(chatModelProperties.getMaxRetries()).proxy(ProxyProperties.convert(chatModelProperties.getProxy())).logRequests(chatModelProperties.getLogRequests()).logResponses(chatModelProperties.getLogResponses()).customHeaders(chatModelProperties.getCustomHeaders()).build();
    }

chatAi :LLM API 调用

   @Bean
    public ChatLanguageModel chatLanguageModel() {
        return OpenAiChatModel.builder()
                .apiKey(apiKey)
                .modelName("qwen-turbo")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    @Bean
    public ChatAssistant chatAssistant(ChatLanguageModel chatLanguageModel){
        return AiServices.create(ChatAssistant.class,chatLanguageModel);
    }

chat流式输出

使用spring controller进行web端的调用


/**
* 测试controller
* lp
* 2025年02月19日15:54:39
* */

@RestController
@RequiredArgsConstructor
@RequestMapping("/test01")
public class ChatTestController01 {

   private final StreamChatAssistant streamChatAssistant;

   /**
    * 前端流式调用
    * @param question
    * http://127.0.0.1:8080/test01/chat/stream?question="hello"
    * */
   @GetMapping("/chat/stream")
   public Flux<String> chat(@RequestParam("question") String question){
       return streamChatAssistant.chat(question);
   }

}

chat 的记忆持久化:ChatMemory、实现ChatMemoryStore接口进行自定义持久存储。
在默认情况下持久化内容存储存在内存中

问题解决:有两个 TestBean 类型的 bean注入问题时候。
为了明确指定在创建 时使用哪一个 TestBean,可以在参数上使用 @Qualifier 注解。
以下是具体的修改步骤:
在需要注入的地方使用 @Qualifier 注解:
参数中添加 @Qualifier("TestBean01"),以明确指定使用 TestBean01 这个 bean。
如果你需要在其他地方使用 TestBean02,也可以类似地使用 @Qualifier("TestBean02")。

提示词工程:系统级提示词、用户提示词模板

SystemMessage具有高优先级,能有效地指导模型的整体行为

1. 使用 @UserMessage 和 @V 注解:
public interface AiAssistant {
    @SystemMessage("你是一位专业的中国法律顾问,只回答与中国法律相关的问题。输出限制:对于其他领域的问题禁止回答,直接返回'抱歉,我只能回答中国法律相关的问题。'")
    @UserMessage("请回答以下法律问题:{{question}}")
    String answerLegalQuestion(@V("question") String question);
}


2. 使用 @StructuredPrompt 定义结构化提示:
@Data
@StructuredPrompt("根据中国{{legal}}法律,解答以下问题:{{question}}")
class LegalPrompt {
    private String legal;
    private String question;
}


3. 使用 PromptTemplate 渲染:
// 默认 form 构造使用 it 属性作为默认占位符
PromptTemplate template = PromptTemplate.from("请解释中国法律中的'{{it}}'概念。");
Prompt prompt = template.apply("知识产权");
System.out.println(prompt.text()); // 输出: 请解释中国法律中的'知识产权'概念。

// apply 方法接受 Map 作为参数
PromptTemplate template2 = PromptTemplate.from("请解释中国法律中的'{{legal}}'概念。");
Prompt prompt2 = template2.apply(Map.of("legal", "知识产权"));
System.out.println(prompt2.text());

Json格式化输出数据
让大模型输出我们想要的格式的数据方式有几种:指定类型、根据枚举、自定义pojo类
原理为:使用提示词限定结果的输出

/**
     * 返回一个pojo自定义数据类型
     * 可以使用@Description 注解帮助大模型理解字段含义
     * */
    @UserMessage("从数据中提取 person 信息 {{it}}")
    Person extractPerson(String text);

    @Data
    class Person {
        @Description("姓名") // 增加字段描述,让大模型更理解字段含义
        private String name;
        private LocalDate birthDate;
    }

-------
请求:
 method: POST
- url: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
- headers: [Authorization: Bearer sk-cf...b4], [User-Agent: langchain4j-openai]
- body: {
  "model" : "qwen-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "从数据中提取 person 信息 我叫小明,今年18岁,我是一个学生,我的生日是2005年8月10日\nYou must answer strictly in the following JSON format: {\n\"name\": (姓名; type: string),\n\"birthDate\": (type: date string (2023-12-31))\n}"
  } ],
  "temperature" : 0.7
}
结果:
{"choices":[{"message":{"role":"assistant","content":"```json\n{\n  \"name\": \"小明\",\n  \"birthDate\": \"2005-08-10\"\n}\n```"}

注意点:如果没有提取到对应的数据,可能会导致返回的内容错误或者出现异常,需要对大模型额外针对错误数据进行提示处理:

  @UserMessage("从数据中提取 person 信息 {{it}}。如果没有提取到,则直接回复:“(name=null, birthDate=1900-01-01)")
    Person extractPerson(String text);

函数调用: 触发外部操作:如发送邮件、控制智能家居设备等
方式:

编码注入函数、注解注入函数、动态工具配置

@Bean
public FunctionAssistant functionAssistant(ChatLanguageModel chatLanguageModel) {
    // 工具说明 ToolSpecification
    ToolSpecification toolSpecification = ToolSpecification.builder()
            .name("invoice_assistant")
            .description("根据用户提交的开票信息,开具发票")
            .addParameter("companyName", type("string"), description("公司名称"))
            .addParameter("dutyNumber", type("string"), description("税号"))
            .addParameter("amount", type("number"), description("金额"))
            .build();

    // 业务逻辑 ToolExecutor
    ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -> {
        String arguments1 = toolExecutionRequest.arguments();
        System.out.println("arguments1 =>>>> " + arguments1);
        return "开具成功";
    };

    return AiServices.builder(FunctionAssistant.class)
            .chatLanguageModel(chatLanguageModel)
            .tools(Map.of(toolSpecification, toolExecutor))
            .build();
}

动态函数调用:
接入GraalVM,实现动态函数调用,直接执行代码给出结果,而不是通过推理

CodeExecutionEngine engine = new GraalVmJavaScriptExecutionEngine();

String code = """
        function fibonacci(n) {
            if (n <= 1) return n;
            return fibonacci(n - 1) + fibonacci(n - 2);
        }
                        
        fibonacci(10)
        """;

String result = engine.execute(code);

ChatAssistant assistant = AiServices.builder(ChatAssistant.class)
        .chatLanguageModel(chatLanguageModel)
        .tools(new GraalVmJavaScriptExecutionTool())
        .build();

String chat = chatAssistant.chat("What is the square root of 485906798473894056 in scientific notation?");
System.out.println(chat);

引入的 graalVm 依赖,通过GraalVmJavaScriptExecutionTool类实现了JavaScript代码的动态执行

函数增强搜索:
接入SearchApi 实现大模型直接访问web进行查询数据

public interface ChatAssistant {

    @SystemMessage("""
                1.  搜索支持:你的职责是为用户提供基于网络搜索的支持。
                2.  事件验证:如果用户提到的事件尚未发生或信息不明确,你需要通过网络搜索确认或查找相关信息。
                3.  网络搜索请求:使用用户的查询创建网络搜索请求,并通过网络搜索工具进行实际查询。
                4.  引用来源:在最终回应中,必须包括搜索到的来源链接,以确保信息的准确性和可验证性。
            """)
    String chat(String message);
}

@Bean
public ChatAssistant chatAssistant(ChatLanguageModel chatLanguageModel) {
    SearchApiWebSearchEngine searchEngine = SearchApiWebSearchEngine.builder()
            .apiKey("p8SZVNAweqTtoZBBTVnXttcj")// 测试使用
            .engine("google")
            .build();

    WebSearchTool webSearchTool = WebSearchTool.from(searchEngine);
    return AiServices.builder(ChatAssistant.class).chatLanguageModel(chatLanguageModel).tools(webSearchTool).build();
}

String chat = chatAssistant.chat("20241008 上证指数是多少");

System.out.println(chat);

截至2024年10月8日,A股市场迎来了国庆节后的首个交易日,主要指数表现强劲。具体到上证指数,开盘时涨幅达到了10.13%,但之后的走势有所调整,最终收盘时上证指数报收于3489.78点,涨幅为4.59%。这一天,沪深两市的成交额均非常活跃,接近或超过3.5万亿元人民币。

以上信息来源于多个新闻源,包括财新网、观察者网、中国新闻网、新浪财经等,您可以点击提供的链接查看更详细的信息和报道背景。请注意,这些数据和分析仅供参考,市场情况可能会随时变化。

向量化及存储:
Embedding模型是将文本数据(如词汇、短语或句子)转换为数值向量的工具,将文本映射到高维空间中的点,使语义相似的文本在这个空间中距离较近.

    @Value("${dashscope.api.key}")
    private String apiKey;
    private String qdrantHost="127.0.0.1";
    private int qdrantPort=6334;

    public static String collectionName="testEmbedding01";

    /**
     * 创建Qdrant客户端
     * */
    @Bean
    public QdrantClient qdrantClient() {
        QdrantGrpcClient.Builder grpcClientBuilder = QdrantGrpcClient.newBuilder(qdrantHost, qdrantPort, false);
        return new QdrantClient(grpcClientBuilder.build());
    }

    /**
     *使用OpenAI的Embedding模型进行文本向量化
     * 这里使用的是阿里百炼平台 text-embedding-v3 向量模型
     * */
    @Bean
    public EmbeddingModel embeddingModel() {
        return OpenAiEmbeddingModel.builder()
                .apiKey(apiKey)
                .modelName("text-embedding-v3")
                .logResponses(true)
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }


    /**
     * 配置Qdrant Embedding Store
     * */
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        return QdrantEmbeddingStore.builder()
                .host(qdrantHost)
                .port(qdrantPort)
                .collectionName(collectionName)
                .build();
    }

文本向量化分类:

TextClassifier接口,结合向量模型,可以完成文本信息的分类,可以应用到性格,心理等测试之中


    /**
     * 简单的例子:
     * 通过TextClassifier,以及文本向量化分类(只支持枚举类型的分类)
     * */
    @Bean
    public EmbeddingModelTextClassifier<PersonalityTrait> textClassifier(EmbeddingModel embeddingModel) {
        return new EmbeddingModelTextClassifier(embeddingModel, PersonalityTraitExamples.examples);
    }
/**
     * 文本向量化分类 测试
     * 使用textClassifer,通过EmbeddingModel 向量大模型,最后根据枚举类型得到分类结果
     * 可以应用在性格判断,比如说心理方面的评估
     * */
    @Test
    void textClassifier() {
        List<PersonalityTrait> personalityTraitList= textClassifier.classify("我最喜欢和别人一起工作了,让我可以学到东西,也可以帮助到其他人,收获很多,通过帮助团队的人,我们可以一起进步和学习");
        System.out.println("返回最匹配的性格分类");
        System.out.println(personalityTraitList);
    }

RAG:检索增强生成(Retrieval-Augmented Generation,简称RAG)是一种结合大型语言模型(LLM)和外部知识库的技术
两个阶段:索引(预处理和存储文档数据为向量数据)和检索(将用户搜索转换为向量进行搜索查询)

检索增强器(Retrieval Augmentor)

@Bean
public ChatAssistant assistant(ChatLanguageModel chatLanguageModel, EmbeddingStore<TextSegment> embeddingStore) {

    DefaultRetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
            .queryTransformer()  // 查询增强
            .contentRetriever() // 内容源 单个直接配置
            .queryRouter(new DefaultQueryRouter())// 多个内容源,路由
            .contentAggregator() // 匹配结果聚合
            .contentInjector()   // 结果提示词注入
            .executor()  // 并行化
            .build();

    return AiServices.builder(ChatAssistant.class)
            .chatLanguageModel(chatLanguageModel)
            .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
            .retrievalAugmentor(retrievalAugmentor)
            .build();
}

调用本地部署的deepseek模型
参考链接1
参考链接2

@SpringBootTest
public class OllamaTest {
   //首先在装有ollama的电脑上启动 : ollama serve, 然后启动模型:ollama run deepseek-r1:7b
   String url = "http://192.168.110.77:11434";
   String modelName = "deepseek-r1:7b" ;
   String embedingModel="bge-m3:latest";
   @Test
    void test(){
       LanguageModel model = OllamaLanguageModel.builder()
               .baseUrl(url)
               .modelName(modelName)
               .build();
       String result = model.generate("你是谁").content();
       System.out.println(result);

   }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容