Java 使用LangChain4j集成大模型入门教程兼容deepSeek   

       LangChain是一个用于开发由大型语言模型驱动的应用程序的框架(LLMs)。LangChain 4j的目标是简化LLMs到Java应用程序的集成。

 1.模型的接入

首先我们完成一个最简单的对话示例。

第一步,接入大模型,我们这里以硅基流动为例去申请一个key(其余大模型平台流程基本一样)

硅基流动地址 https://cloud.siliconflow.cn/

我们登陆后需要申请key  (记录秘钥,后续使用)

我们可以在模型广场根据需要选择模型,这里以ds-v3为例,复制模型名称

第二步,新建一个java工程,引入LangChain 4j的基本依赖信息

       <dependency>

            <groupId>dev.langchain4j</groupId>

            <artifactId>langchain4j-open-ai</artifactId>

            <version>1.0.0-beta1</version>

        </dependency>

        <dependency>

            <groupId>dev.langchain4j</groupId>

            <artifactId>langchain4j</artifactId>

            <version>1.0.0-beta1</version>

        </dependency>

  

第三步,调用大模型api

1、构建模型连接对象

  public static OpenAiChatModel deepseek = OpenAiChatModel.builder()

            .apiKey("xxxxx")

            .modelName("Pro/deepseek-ai/DeepSeek-V3")

            .baseUrl("https://api.siliconflow.cn/v1")

            //在0和2之间。较高的值(如0.8)将使输出更随机,而较低的值(如0.2)将使其更集中和确定性。

            .temperature(0.8)

            //聊天完成时可以生成的令牌的最大数量。

            .maxTokens(4096)

            //介于-2.0和2.0之间的数字。正值会根据新标记在文本中的现有频率对新标记进行惩罚,从而降低模型逐字重复同一行的可能性。

            //.frequencyPenalty()

            //更多参数请参考  https://docs.langchain4j.dev/integrations/language-models/open-ai/

            .build();

2、调用模型同步返回结果

   String answer = deepseek.generate("北京是哪里的首都");

   System.out.println(answer); 

运行结果 如下

北京是**中华人民共和国**的首都,位于中国北部,华北平原的北部边缘。作为中国的政治、文化、国际交往和科技创新中心,北京拥有悠久的历史和丰富的文化遗产,是世界上最重要的城市之一。

2.模型角色设置

有时候我们需要给ai设置一个角色来满足我们的应用需求

通过 SystemMessage 对象,可以给ai设置角色或自定义信息,用ChatMessage 来封装问

        ChatMessage systemMessage = new SystemMessage("你是一个什么都不懂的傻子,别人问什么你都要说不知道");

        ChatMessage userMessage = new UserMessage("北京是哪里的首都");

        List<ChatMessage> list = new ArrayList<>();

        list.add(userMessage);

        list.add(systemMessage);

        Response<AiMessage> generate = deepseek.generate(list);

        System.out.println(generate.content().text()); 

返回信息如下

不知道。

2.模型上下文设置

有时候我们的提问是多轮对话,需要模型记住上下文(注意不要超过模型的最大token数)

        UserMessage firstUserMessage = UserMessage.from("Hello, 我的名字是小红");

        AiMessage firstAiMessage = deepseek.generate(firstUserMessage).content(); // Hi Klaus, how can I help you?

        System.out.println(firstAiMessage.text());

        UserMessage secondUserMessage = UserMessage.from("你还记得我叫什么吗?");

        AiMessage secondAiMessage = deepseek.generate(firstUserMessage, firstAiMessage, secondUserMessage).content(); // Klaus

        System.out.println(secondAiMessage.text());

运行结果如下

你好,小红!很高兴认识你。请问有什么我可以帮你的吗?

当然记得,你刚刚告诉我你的名字是小红。有什么我可以为你做的吗,小红?

  1. 多模态模型使用(需要模型支持)

在某些情况下,我们会给模型传入图片、视频文件等多种类型的入参让模型参与分析,支持多种文件输入方式的模型就是支持多模态的模型(也就是说需要模型支持才能使用)。

这里我以通义千问的全模态模型qwen-omni-turbo作为示例(该模型只支持异步调用)

public static void multiModalImage() throws Exception {

#构建异步流式传输的模型对象

        OpenAiStreamingChatModel allModel = OpenAiStreamingChatModel.builder().apiKey("xxxx")

                .modelName("qwen-omni-turbo")

                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")

                .build();

#需要将图片转为base64后作为参数传入

        File file = new File("C://Users//82129//Desktop//ss.png");

        final byte[] bytes = Files.readAllBytes(file.toPath());

        String base64Data = Base64.getEncoder().encodeToString(bytes);

#封装图片文件上下文

        ImageContent imageContent = ImageContent.from(base64Data, "image/png");

        UserMessage userMessage = UserMessage.from(imageContent);

        #封装问题

        UserMessage userMessage2 = UserMessage.from("请描述一下图片中的内容");

         #封装异步请求,获得返回结构(结果流式返回)

        ChatRequest chatRequest = ChatRequest.builder().parameters(OpenAiChatRequestParameters

                .builder().modelName("qwen-omni-turbo").build()).messages(userMessage, userMessage2).build();

        allModel.doChat(chatRequest, new StreamingChatResponseHandler() {

            @Override

            public void onPartialResponse(String s) {

                System.out.print(s);

            }

            @Override

            public void onCompleteResponse(ChatResponse chatResponse) {

                System.out.println();

                System.out.print("回答完毕");

                final FinishReason finishReason = chatResponse.finishReason();

            }

            @Override

            public void onError(Throwable throwable) {

                System.out.print("出错了!!!");

                System.out.println(throwable.getMessage());

            }

        });

    }

图片如下

回答如下

这张图片是一张阿里云的认证证书。证书上显示以下信息:

- 颁发机构:阿里巴巴云(Alibaba Cloud

- 认证名称:Alibaba Cloud Certification

- 获得者姓名:刘海潮

- 成功完成要求并通过获得该证书

- 专项技能认证:阿里云Apsara Clouder

- 证书IDCLDC15230200898192

- 有效日期:至202521

- 签名:阿里云总裁

证书底部有一个金色的徽章,上面写着“CLOUDER”

回答完毕

4.模型记忆

在有些时候我们需要持久化我们的上下文,可以通过如下方式去实现(基于滑动窗口)

首先创建一个接口,其中两个参数,第一个是上下文id,第二个是用户提问信息

    interface Assistant {

        String chat(@MemoryId int memoryId, @dev.langchain4j.service.UserMessage String userMessage);

    }

        Assistant assistant = AiServices.builder(Assistant.class)

                .chatLanguageModel(这里传入模型对象)

// MessageWindowChatMemory.withMaxMessages(10)这是一个容量为10的滑动窗口

                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10)) 

// 为每个 memoryId 创建独立的 ChatMemory 实例

                .build();

        System.out.println(assistant.chat(1, "你好, 我的名字是小红"));

        System.out.println(assistant.chat(2, "你好, 我的名字是小明"));

        System.out.println(assistant.chat(1, "我的名字叫什么?"));

        System.out.println(assistant.chat(2, "我的名字叫什么?"));

回答如下

你好,小红!很高兴认识你!今天有什么我可以帮你的吗?

你好,小明!很高兴认识你。今天有什么我可以帮你的吗?或者有什么你想聊的话题吗?

你的名字是小红。如果你有任何问题或需要帮助,随时告诉我!

你的名字是小明。记得吗?如果还有其他问题或需要帮助的地方,随时告诉我哦!

以上方式是将上下文存入了内存中,若我们需要持久化上下文,可以通过实现ChatMemoryStore来自定义持久化逻辑(示例中是使用map来存储的,实际应用可以改为数据库)

class PersistentChatMemoryStore implements ChatMemoryStore {

    private final Map<Integer, String> map = new HashMap<>();

    @Override

    public List<ChatMessage> getMessages(Object memoryId) {

        String json = map.get((int) memoryId);

        return messagesFromJson(json);

    }

    @Override

    public void updateMessages(Object memoryId, List<ChatMessage> messages) {

        String json = messagesToJson(messages);

        map.put((int) memoryId, json);

    }

    @Override

    public void deleteMessages(Object memoryId) {

        map.remove((int) memoryId);

    }

    public static void main(String[] args) {

        ChatMemory chatMemory = MessageWindowChatMemory.builder()

                .id(1)

                .maxMessages(10)

                .chatMemoryStore(new PersistentChatMemoryStore())

                .build();

        c模型记忆.Assistant assistant = AiServices.builder(c模型记忆.Assistant.class)

                .chatLanguageModel(a模型调用.deepseek)

                .chatMemoryProvider(memoryId -> chatMemory)

                .build();

        System.out.println(assistant.chat(1, "你好, 我的名字是小红"));

        System.out.println(assistant.chat(1, "你好, 我今年18岁了,喜欢吃橘子"));

        System.out.println(assistant.chat(1, "我的名字叫什么?多大年纪?喜欢吃什么"));

    }

}

返回结果是

你好,小红!很高兴认识你。今天有什么我可以帮助你的吗?无论是学习、工作还是生活中的问题,随时告诉我哦。

你好!很高兴听到你喜欢吃橘子。橘子不仅美味,还富含维生素C和纤维,对健康非常有益。你平时是直接吃,还是喜欢用橘子来做其他美食呢?比如橘子汁或者橘子沙拉?

你的名字是小红,今年18岁,喜欢吃橘子。这些信息都很有特点哦!如果你还有其他有趣的爱好或者想要分享的事情,随时告诉我,我很乐意听你讲述。

5.流式调用

有时候我们希望模型能逐字给我们返回结果,效果就类似chatGPT聊天框那样的效果,可参考如下代码或上面多模态模型的调用方式

   interface Assistant {

        TokenStream chat(@dev.langchain4j.service.UserMessage String userMessage);

    }

    public static void tokenStream() {

        OpenAiStreamingChatModel openAiStreamingChatModel = OpenAiStreamingChatModel.builder()

                .apiKey("xxx")

                .modelName("deepseek-v3")

                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")

                //在0和2之间。较高的值(如0.8)将使输出更随机,而较低的值(如0.2)将使其更集中和确定性。

                .temperature(0.8)

                //聊天完成时可以生成的令牌的最大数量。

                .maxTokens(300)

                .build();

        Assistant assistant = AiServices.builder(Assistant.class)

                .streamingChatLanguageModel(openAiStreamingChatModel)

                .build();

        TokenStream tokenStream = assistant.chat("给我讲一个笑话,不超过100字");

        tokenStream

                .onPartialResponse(System.out::println)

                .onRetrieved(System.out::println)

                .onToolExecuted(System.out::println)

                .onCompleteResponse(System.out::println)

                .onError(Throwable::printStackTrace)

                .start();

    }

6.返回结果自动封装成对象(有些模型效果不好)

有时候我们希望模型的返回结果直接封装成一个java对象方便我们使用。

首先我们引入依赖(该示例依托langchain4j-spring-boot-starter)

  

     <dependency>

            <groupId>dev.langchain4j</groupId>

            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>

            <version>1.0.0-beta1</version>

        </dependency>

        <dependency>

            <groupId>dev.langchain4j</groupId>

            <artifactId>langchain4j-reactor</artifactId>

            <version>1.0.0-beta1</version>

        </dependency>

        <dependency>

            <groupId>dev.langchain4j</groupId>

            <artifactId>langchain4j-spring-boot-starter</artifactId>

            <version>1.0.0-beta1</version>

        </dependency>

然后在项目的配置文件中配置模型连接信息

langchain4j.open-ai.chat-model.api-key=xxx  模型key

langchain4j.open-ai.chat-model.base-url=xxx  模型调用地址

langchain4j.open-ai.chat-model.model-name=qwen-plus 模型名称

langchain4j.open-ai.chat-model.log-requests=true

langchain4j.open-ai.chat-model.log-responses=true

首先我们需要将希望模型返回的对象使用@Description注解标注,如下所示

@Data

@Description("an person")

public class Person implements Serializable {

    // you can add an optional description to help an LLM have a better understanding

    String firstName;

    String lastName;

    LocalDate birthDate;

    Address address;

}



@Description("an address")

@Data

public class Address {

    String street;

    Integer streetNumber;

    String city;

}

然后我们需要自定义一个service接口(注意将返回结果设置为对象{{it}}是占位符)

@AiService

public interface Assistant {

    @UserMessage("Extract information about a person from {{it}}")

    Person extractPersonFrom(String text);

}

最后调用模型 注意直接注入Assistant 接口即可使用

  /*模型连接信息写到了配置文件里(返回对象需要模型支持,有的模型不支持)*/

    @Autowired

    Assistant assistant;

    @Test

    void contextLoads() {

        /*结果转成对象*/

        String text = """

                In 1968, amidst the fading echoes of Independence Day,

                a child named John arrived under the calm evening sky.

                This newborn, bearing the surname Doe, marked the start of a new journey.

                He was welcomed into the world at 345 Whispering Pines Avenue

                a quaint street nestled in the heart of Springfield

                an abode that echoed with the gentle hum of suburban dreams and aspirations.

                """;

        Person person = assistant.extractPersonFrom(text);

        System.out.println(JSONObject.toJSONString(person));

    }

返回结果如下:

{"address":{"city":"Springfield","street":"Whispering Pines Avenue","streetNumber":345},"birthDate":"1968-07-04","firstName":"John","lastName":"Doe"}

7.函数调用 function call(需要模型支持)

有些模型支持函数调用Tools,可以根据问答结果调用代码中定义的特殊函数

首先我们要先自定义函数

为了方便理解,我们把加法和乘法的结果都*2,且在函数中打印了日志,可以观察函数是否被调用


@Slf4j

public class Tools {

    /*函数调用*/

    @Tool

    int add(int a, int b) {

        log.info("add 结果是 :{}", (a + b) * 2);

        return (a + b) * 2;

    }

    @Tool

    int multiply(int a, int b) {

        log.info("multiply 结果是 :{}", a * b * 2);

        return a * b * 2;

    }

}

定义@AiService

@AiService

public interface Assistant {

    String chat(String userMessage);

}

封装Tools并调用

  可以观察到日志里函数里的日志已打印,且返回的结果*2,在该模式下模型的返回结果是函数的调用流程

     OpenAiChatModel guijids = OpenAiChatModel.builder()

                .apiKey("模型key")

                .modelName("deepseek-ai/DeepSeek-V2.5")

                .baseUrl("模型调用地址")

                //在0和2之间。较高的值(如0.8)将使输出更随机,而较低的值(如0.2)将使其更集中和确定性。

                .temperature(0.8)

                //聊天完成时可以生成的令牌的最大数量(超过模型最大支持数会报错)

                .maxTokens(4096)

                .build();

        /*模型暂不支持*/

        Assistant assistant = AiServices.builder(Assistant.class)

                .chatLanguageModel(guijids)

                .tools(new Tools()) // 封装函数逻辑

                .build();

        String answer = assistant.chat("What is 1+2 and 3*4?");

        System.out.println(answer);

日志打印如下

15:45:18.832 [main] INFO com.lhc.langchainjavatest.bSpringBoot集成.Tools -- add 结果是 6

15:45:18.842 [main] INFO com.lhc.langchainjavatest.bSpringBoot集成.Tools -- multiply 结果是 24

15:45:24.197 [main] INFO com.lhc.langchainjavatest.bSpringBoot集成.Tools -- multiply 结果是 24

<tool▁calls▁begin>function<tool▁sep>add

```json

{"a":1,"b":2}

```<tool▁call▁end>

<tool▁calls▁begin>function<tool▁sep>multiply

```json

{"a":3,"b":4}

```<tool▁call▁end><tool▁calls▁end>

8.集成向量RAG进行调用(需要向量模型)

大模型支持的上下文是有限的,假设我们要基于一个文档来提问,我们不可能将整个文档都作为上下文来提问,这样会超出模型的限制进而报错,这时候我们需要引入向量模型和向量数据库。

检索增强生成(Retrieval-augmented Generation),简称RAG,是当下热门的大模型前沿技术之一 。

检索增强生成模型结合了语言模型和信息检索技术。具体来说,当模型需要生成文本或者回答问题时,它会先从一个庞大的文档集合中检索出相关的信息,然后利用这些检索到的信息来指导文本的生成,从而提高预测的质量和准确性。

嵌入(Embedding)的工作原理是将文本、图像和视频转换为称为向量(Vectors)的浮点数数组。这些向量旨在捕捉文本、图像和视频的含义。嵌入数组的长度称为向量的维度(Dimensionality)。

嵌入模型(EmbeddingModel)是嵌入过程中采用的模型。

使用流程如下

  1. 将文本分块(防止超出向量模型上下文最大限制)
  2. 通过向量模型将文本块转为向量索引
  3. 将向量索引和对应的文本块存入向量数据库
  4. 将用户输入通过向量模型转换为向量
  5. 通过向量数据库找出最相似的N条文本块
  6. 封装Prompt,将相似文本连同问题传给LLM大模型进行问答

为了方便,该示例使用内存向量数据库,在生产中应使用独立的向量库

public class aEasyRAG {

    public static void main(String[] args) {

        /*向量模型*/

        final EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()

                .modelName("netease-youdao/bce-embedding-base_v1")

                .apiKey("xxx")

                .baseUrl("https://api.siliconflow.cn/v1").build();

        OpenAiChatModel qwenPlus = OpenAiChatModel.builder()

                .apiKey("xxx")

                .modelName("qwen-plus")

                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")

                //在0和2之间。较高的值(如0.8)将使输出更随机,而较低的值(如0.2)将使其更集中和确定性。

                .temperature(0.8)

                //聊天完成时可以生成的令牌的最大数量。

                .maxTokens(1000)

                .build();

        /*加载文档*/

        /*ApacheTikaDocumentParser  该库可以解析大部分类型文档*/

        Document document = loadDocument("C://Users//82129//Desktop//did介绍.pdf", new ApacheTikaDocumentParser());

        /*切割文档*/

        DocumentSplitter splitter = DocumentSplitters.recursive(300, 10);

        List<TextSegment> segments = splitter.split(document);

        /*使用向量模型处理返回向量数据*/

        List<Embedding> embeddings = embeddingModel.embedAll(segments).content();

        /*存入向量数据库(内存数据库,在生产环境中根据情况需要使用第三方向量数据库如chroma)*/

        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

        embeddingStore.addAll(embeddings, segments);

        //为了在向量库中查询到相似的知识片段,作为查询的文本也需要进行向量化,方法同上。

        Embedding queryEmbedding = embeddingModel.embed("简述一did字符串的生成规则").content();

        EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding).maxResults(2).build();

        EmbeddingSearchResult<TextSegment> embeddedEmbeddingSearchResult = embeddingStore.search(embeddingSearchRequest);

        List<EmbeddingMatch<TextSegment>> embeddingMatcheList = embeddedEmbeddingSearchResult.matches();

        EmbeddingMatch<TextSegment> embeddingMatch = embeddingMatcheList.getFirst();

        TextSegment textSegment = embeddingMatch.embedded();

        PromptTemplate promptTemplate = PromptTemplate.from("基于如下信息进行回答:\n" +

                "{{context}}\n" +

                "提问:\n" +

                "{{question}}");

        Map<String, Object> variables = new HashMap<>();

        variables.put("context", textSegment.text());

        variables.put("question", QUESTION);

        Prompt prompt = promptTemplate.apply(variables);

        UserMessage userMessage = prompt.toUserMessage();

        final ChatResponse chat = qwenPlus.chat(userMessage);

        System.out.println(chat.aiMessage().text());

    }

}

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐