LangChain4j入门手册

软件安装

一切以快速入手为目的,安装ollama。并通过指令 ollama run deepseek-r1:1.5b  安装简约版的deepseek。ollama可以理解为ai的docker,通过创建镜像容器运行对应的大模型版本。并且ollama也有ps指令查看当前的容器情况。(镜像的版本可以自由选择,再次我安装的是deepseek简约吧,镜像越大当然对应着功能越全面)

AIService

本质上就是一个现成的一个注解作用在自定义的接口上,主要的作用生成被修饰接口具有ai方法的代理bean,在我们需要使用该代理bean的时候,通过常规的依赖注入即可使用。

引入依赖

        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
            <version>1.2.0-beta8</version>
        </dependency>

代码格式为下:

import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;


//wiringMode默认使用AUTO模式自动注入所有工具类,EXPLICIT需要指定工具类,需要设置tool
//chatModel如果没有设置则会在创建代理bean的时候到bean容器中找到ChatModel的实现类

@AiService(wiringMode = EXPLICIT, chatModel = "ollamaChatModel")
public interface AIAssistant {
    String chat(String userMessage);
}

wiringMode默认使用AUTOMATIC模式自动注入所有的工具类,chatModel如果没有设置则会在创建代理bean的时候到bean容器中找到ChatModel的实现类,如果都是使用默认的配置,需要保证在在ioc容器中只能有一个ChatModel接口的实现类,否则会报存在bean不唯一的错误。在完成接口的配置后,在使用位置@AutoWired依赖注入即可使用。

    @Autowired
    AIAssistant aiAssistant;
    @Test
    public void test1() {
        String chat = aiAssistant.chat("你好吗?");
        System.out.println(chat);
    }

结果为下:

自动创建代理bean实现原理

@Bean
    BeanFactoryPostProcessor aiServicesRegisteringBeanFactoryPostProcessor() {
        return beanFactory -> {
                    
            // 找到在bean容器中所有的相关配置的bean结合
            String[] chatModels = beanFactory.getBeanNamesForType(ChatModel.class);
            String[] streamingChatModels = beanFactory.getBeanNamesForType(StreamingChatModel.class);
            String[] chatMemories = beanFactory.getBeanNamesForType(ChatMemory.class);
            String[] chatMemoryProviders = beanFactory.getBeanNamesForType(ChatMemoryProvider.class);
            String[] contentRetrievers = beanFactory.getBeanNamesForType(ContentRetriever.class);
            String[] retrievalAugmentors = beanFactory.getBeanNamesForType(RetrievalAugmentor.class);
            String[] moderationModels = beanFactory.getBeanNamesForType(ModerationModel.class);

            Set<String> toolBeanNames = new HashSet<>();
            List<ToolSpecification> toolSpecifications = new ArrayList<>();
//不相甘

            for (String beanName : beanFactory.getBeanDefinitionNames()) {
                try {
                    String beanClassName = beanFactory.getBeanDefinition(beanName).getBeanClassName();
                    if (beanClassName == null) {
                        continue;
                    }
                    Class<?> beanClass = Class.forName(beanClassName);
                    for (Method beanMethod : beanClass.getDeclaredMethods()) {
                        if (beanMethod.isAnnotationPresent(Tool.class)) {
                            toolBeanNames.add(beanName);
                            try {
                                toolSpecifications.add(ToolSpecifications.toolSpecificationFrom(beanMethod));
                            } catch (Exception e) {
                                log.warn("Cannot convert %s.%s method annotated with @Tool into ToolSpecification"
                                        .formatted(beanClass.getName(), beanMethod.getName()), e);
                            }
                        }
                    }
                } catch (Exception e) {
                    // TODO
                }
            }

//不相甘


//查找所有被AiService注释的接口
            String[] aiServices = beanFactory.getBeanNamesForAnnotation(AiService.class);
            for (String aiService : aiServices) {
//获取反射
                Class<?> aiServiceClass = beanFactory.getType(aiService);
//设置GenericBeanDefinition的基本配置(配置工厂类,配置需要生成的实体类的接口)
                GenericBeanDefinition aiServiceBeanDefinition = new GenericBeanDefinition();
                aiServiceBeanDefinition.setBeanClass(AiServiceFactory.class);
                aiServiceBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aiServiceClass);
                MutablePropertyValues propertyValues = aiServiceBeanDefinition.getPropertyValues();
 //获取在注解上的配置
                AiService aiServiceAnnotation = aiServiceClass.getAnnotation(AiService.class);
//注入chatModel的bean
                addBeanReference(
                        ChatModel.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.chatModel(),
                        chatModels,
                        "chatModel",
                        "chatModel",
                        propertyValues
                );
//注入streamingChatModel的bean
                addBeanReference(
                        StreamingChatModel.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.streamingChatModel(),
                        streamingChatModels,
                        "streamingChatModel",
                        "streamingChatModel",
                        propertyValues
                );
//注入chatMemory的bean(聊天记忆类的bean)
                addBeanReference(
                        ChatMemory.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.chatMemory(),
                        chatMemories,
                        "chatMemory",
                        "chatMemory",
                        propertyValues
                );
//注入会话隔离的bean

                addBeanReference(
                        ChatMemoryProvider.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.chatMemoryProvider(),
                        chatMemoryProviders,
                        "chatMemoryProvider",
                        "chatMemoryProvider",
                        propertyValues
                );

                addBeanReference(
                        ContentRetriever.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.contentRetriever(),
                        contentRetrievers,
                        "contentRetriever",
                        "contentRetriever",
                        propertyValues
                );

                addBeanReference(
                        RetrievalAugmentor.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.retrievalAugmentor(),
                        retrievalAugmentors,
                        "retrievalAugmentor",
                        "retrievalAugmentor",
                        propertyValues
                );

                addBeanReference(
                        ModerationModel.class,
                        aiServiceAnnotation,
                        aiServiceAnnotation.moderationModel(),
                        moderationModels,
                        "moderationModel",
                        "moderationModel",
                        propertyValues
                );
                //注入指定的工具类
                if (aiServiceAnnotation.wiringMode() == EXPLICIT) {
                    propertyValues.add("tools", toManagedList(asList(aiServiceAnnotation.tools())));
                } else if (aiServiceAnnotation.wiringMode() == AUTOMATIC) {
//注入所有的工具类
                    propertyValues.add("tools", toManagedList(toolBeanNames));
                } else {
                    throw illegalArgument("Unknown wiring mode: " + aiServiceAnnotation.wiringMode());
                }
                //设置beanFactory的配置
                BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                registry.removeBeanDefinition(aiService);
//registerBeanDefinition用来生成接口对应的实现代理bean
                registry.registerBeanDefinition(lowercaseFirstLetter(aiService), aiServiceBeanDefinition);

                if (eventPublisher != null) {
                    eventPublisher.publishEvent(new AiServiceRegisteredEvent(this, aiServiceClass, toolSpecifications));
                }
            }
        };
    }

当在接口上添加@AiService时,就会在项目启动的时候进行自动装配,在容器中扫描所有被该注解修饰接口和扫描注解上的配置,并通过反射的方式在配合bean工厂生成接口的实现代理类。

聊天记忆

在我们使用AiService注解的时候,默认时没有聊天记忆的,也就是每一次调用chat口都是一次新的会话,这明显达不到我们需要的效果,当然为了解决这个问题,我们只需要在注解AiService指明chatMemory实例bean就可以实现此效果。目前最简单的实现方式就时使用MessageWindowChatMemory实现的实例bean。

实现代码为下:
将chatMemory注入bean容器中

import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class memoryConfig {
    @Bean
    public ChatMemory chatMemory() {
//设置最大的会话信息上限为10条
        return MessageWindowChatMemory.withMaxMessages(10);
    }
}

    @Autowired
    AIAssistant aiAssistant;
    @Test
    public void test1() {
        System.out.println(aiAssistant.chat("我叫秃狼,是一个scdn博主"));
        System.out.println(aiAssistant.chat("我是谁?"));
    }

执行代码后输出结果为下:

在第一次输入用户信息后,信息会被记录在message中。

在第二次调用接口的时候,会携带该messags到接口中,等于是会将这两个userMessage都作为作为入参,因为设置的bean,所以会话的最大记忆条数为10条。

会话隔离

在我们与ai进行对话的时候,可能会存在多个会话,但是不希望这个会话会受到不同会话的影响对应的回答结果。

实现代码为下:

将ChatMemoryProvider注入bean容器中。

@Configuration
public class memoryConfig {
    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        //实现接口的匿名内部类
        return memoryId -> MessageWindowChatMemory
                .builder()
                //设置会话的唯一标识
                .id(memoryId)
                //设置会话一次的最大信息数量
                .maxMessages(10)
                .build();
    }
}

设置 AIAssistant接口。

import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;

@AiService(wiringMode = EXPLICIT,
            chatModel = "ollamaChatModel",
        chatMemory = "chatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface AIAssistant {
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

通过设置memoeyId来隔离不同的会话,在我们开发的时候,不同的用户需要一个不同的会话,所以我们可以将userId作为唯一标识。

实现原理

通过store来存储会话中的userMessage,在目前的ChatMemoryStore接口中有两种实现类,

InMemoryChatMemoryStore:通过hashMap来存储userMessage,其中实现会话隔离的依赖就是通过键值对的形式类实现的。
SingleSlotChatMemoryStore:通过ArrayList来实现userMessage,其中通过一个Object属性和一个集合来实现userMessage存储的。

在代码中我们如果需要切换不同的ChatMemoryStore只需要在生成bean实例的设置设置即可。

@Configuration
public class memoryConfig {

    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return memoryId -> MessageWindowChatMemory
                .builder()
                //设置会话的唯一标识
                .id(memoryId)
                //设置会话一次的最大信息数量
                .maxMessages(10)
                //设置不同的userMessage存储类
                .chatMemoryStore(new InMemoryChatMemoryStore())
                .build();
    }
}

目前框架提供的两个实现类都是基于内存存储的,所以在数据持久化上就是一个问题,位置我们就可以基于自定义实现类,来实现会话数据的持久化,我们可以在此过程中将数据存储内存结构更替为数据存储数据库的结构。

Persistence

在数据库的选型上,需要根据公司的业务做对应的选型,作为入门数据的可靠性要求没有那么高,所以我们选择使用Redis来作为持久化成进行测试。

在先前我们找到可以自定义ChatMemoryStore,为此我们自定义一个RedisChatMemoryStore接口,需要实现增删改查三个口。

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Type;

import java.util.ArrayList;
import java.util.List;
@Component
public class RedisChatMemoryStore implements ChatMemoryStore {
    @Autowired
    RedisTemplate redisTemplate;

    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        String res = (String) redisTemplate.opsForValue().get(memoryId);
        if(res == null) {
            return new ArrayList<>();
        }
           return ChatMessageDeserializer.messagesFromJson(res);
    }

    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> chatMessages) {
        String oldData = (String) redisTemplate.opsForValue().get(memoryId);
        if(oldData != null) {
            List<ChatMessage> oldChatMessages = ChatMessageDeserializer.messagesFromJson(oldData);
            if(!CollectionUtils.isEmpty(chatMessages)) {
                for (ChatMessage chatMessage : chatMessages) {
                    oldChatMessages.add(chatMessage);
                }
            }
            redisTemplate.opsForValue().set(memoryId, ChatMessageSerializer.messagesToJson(oldChatMessages));
        } else if(!CollectionUtils.isEmpty(chatMessages)) {
            redisTemplate.opsForValue().set(memoryId, ChatMessageSerializer.messagesToJson(chatMessages));
        }
    }

    @Override
    public void deleteMessages(Object memoryId) {
        redisTemplate.opsForValue().getAndDelete(memoryId);
    }
}

将我们自定义的ChatMemoryStore设置到chatMemoryProvider中。

@Configuration
public class memoryConfig {
    @Autowired
    RedisChatMemoryStore redisChatMemoryStore;

    @Bean
    public ChatMemory chatMemory() {
        return MessageWindowChatMemory.withMaxMessages(10);
    }

    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return memoryId -> MessageWindowChatMemory
                .builder()
                //设置会话的唯一标识
                .id(memoryId)
                //设置会话一次的最大信息数量
                .maxMessages(10)
                .chatMemoryStore(redisChatMemoryStore)
                .build();
    }
}

进行测试我们就会看到会话中的数据都存储到了redis中。

@SpringBootTest
public class aiStudy {

    @Autowired
    AIAssistant aiAssistant;
    @Autowired
    RedisChatMemoryStore redisChatMemoryStore;
    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void test2() {
        aiAssistant.chat(1, "我是小武");
        aiAssistant.chat(1, "我是谁");
        System.out.println(redisTemplate.opsForValue().get(1));
    }

}

运行结果为下:

Prompt

在用户使用到聊天对话的时候,我们可能需要格外对设置聊天提示词,保证用户得到的回答跟为准确,就比如:我们希望每次Ai回答的时候都能以老师的口吻来回答,但是这些提示说为了用户体验,我们肯定是不能让用户感知到的,所以在框架中,提供了添加提示词的功能。

@SysTemMessage

用于控制系统的行为,作用在整个会话中,可以配合@V注解实现系统提示词的拼接。

@AiService(wiringMode = EXPLICIT,
            chatModel = "ollamaChatModel",
        chatMemory = "chatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface AIAssistant {
    @SystemMessage(value = "请使用老师的口吻来回答问题")
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

ai每次进行回答的时候都会被提前定义设置好的行为。

可以配合@V自定义系统提示词,我们对上面的接口继续做改造。

@AiService(wiringMode = EXPLICIT,
            chatModel = "ollamaChatModel",
        chatMemory = "chatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface AIAssistant {
    @SystemMessage(fromResource = "template.txt")
    String chat(@MemoryId int memoryId, @UserMessage String userMessage, @V("role") String role, @V("size") Integer size);
}

并在src/main/resources/ 目录下创建对应的系统提示词模板。

在我们继续测试会发现,系统提示被成功解析出来了。

@UserMessage的语法和@SystemMessage一致,区别主要在于一个是约束系统的行为,一个是约束用户的行为。再此做不做过多赘述了。

整合demo

设置依赖

 <!-- 接入阿里云百炼平台 -->
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
            </dependency>


<dependencyManagement>
        <dependencies>
                <!--引入百炼依赖管理清单-->
                <dependency>
                    <groupId>dev.langchain4j</groupId>
                    <artifactId>langchain4j-community-bom</artifactId>
                    <version>1.2.0-beta8</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
        </dependencies>
    </dependencyManagement>

设置AiAssistant

@AiService(wiringMode = EXPLICIT,
            chatModel = "qwenChatModel",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface AIAssistant {
    @SystemMessage(fromResource = "template.txt")
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

设置系统提示词

你的名字是“小华智能”,你是一家名为“石庭华康门诊部”的智能客服。
你是一个训练有素的医疗顾问和医疗伴诊助手。
你态度友好、礼貌且言辞简洁。
1、请仅在用户发起第一次会话时,和用户打个招呼,并介绍你是谁。
2、作为一个训练有素的医疗顾问:
尚硅⾕
3、配置小智助手
配置持久化和记忆隔离
4、封装对话对象
请基于当前临床实践和研究,针对患者提出的特定健康问题,提供详细、准确且实用的医疗建议。请同时考虑可能的病
因、诊断流程、治疗方案以及预防措施,并给出在不同情境下的应对策略。对于药物治疗,请特别指明适用的药品名
称、剂量和疗程。如果需要进一步的检查或就医,也请明确指示。
3、作为医疗伴诊助手,你可以回答用户就医流程中的相关问题,主要包含以下功能:
AI分导诊:根据患者的病情和就医需求,智能推荐最合适的科室。
AI挂号助手:实现智能查询是否有挂号号源服务;实现智能预约挂号服务;实现智能取消挂号服务。
4、你必须遵守的规则如下:
在获取挂号预约详情或取消挂号预约之前,你必须确保自己知晓用户的姓名(必选)、身份证号(必选)、预约科室。
当被问到其他领域的咨询时,要表示歉意并说明你无法在这方面提供帮助。
5、请在回答的结果中适当包含一些轻松可爱的图标和表情。
6、今天是 {{current_date}}。

创建Controller

@Tag(name = "小华ai")
@RestController
@RequestMapping("/ai")
public class doctorController {
    @Autowired
    AIAssistant aiAssistant;

    @PostMapping("/chat")
    public String chat(@RequestBody CommonRequest commonRequest) {
        return aiAssistant.chat(commonRequest.getMemoryId(), commonRequest.getUserMessage());
    }
}

进行测试,测试地址:http://localhost:8080/doc.html

Tool

我们可以提供设置一系列的方法来提供给ai调用,将这些方法设置到一个主件中并提供@Tool进行修饰,设置到接口的注解中即可。

ToolConfig方法

@Component
public class ToolConfig {

    @Autowired
    RedisTemplate redisTemplate;

    @Tool(name = "预约挂号",value = "判断用户提供的身份证在redis的键值对中是否作为key存储过,如果没有数据就存一份预约数据到redis中")
    public String saveRecord(@P(value = "用户的名字", required = true) String name, @P("身份证") String idCard, @P("预约的科室") String room) {
        String data = (String) redisTemplate.opsForValue().get(idCard);
        if(data == null) {
            redisTemplate.opsForValue().set(idCard, Json.toJson(new Record(name, idCard, room)));
            return "预约成功";
        } else {
            return "已经有预约了";
        }
    }
    @Tool(name = "查看当前的预约", value = "通过用户提供的idcard到redis中查询出预约数据,并返回")
    public String selectRecord(@P(value = "身份证") String idCard) {
        String data = (String) redisTemplate.opsForValue().get(idCard);
        if(data == null) {
            return "没有查询到预约记录";
        }
        Record record = Json.fromJson(data, Record.class);
        return "你的预约记录为" + data;
    }
}

接口中设置

@AiService(wiringMode = EXPLICIT,
            chatModel = "qwenChatModel",
        chatMemoryProvider = "chatMemoryProvider",
        tools = {"toolConfig"}
)
public interface AIAssistant {
    @SystemMessage(fromResource = "template.txt")
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

调用测试

预约测试

查询预约测试

RAG

由于业务的原因,目前的通用模型可能回答的答案不符合用户的需求,需要企业做自定义后,需要对现有的大模型进行二次训练。

目前常用的解决方案有两种:微调大模型,RAG。

微调大模型:需要二次训练,调整参数,且有新知识的时候需要再次微调大模型,这就导致成本很高。
Rag:在问题发送给大模型前,提供外部的知识库先检索,将检索到的数据和问题都发送给大模型,提供大模型的回答的正确性,随着新知识的产生,我们只需要更新知识库,不会涉及到大模型的调整,调整的成本比较低。

RAG:索引、检索。

索引:加载知识库文档 -> 将文档分为多个文本 -> 使用向量大模型将文本转换为向量 -> 将生成的向量存储到向量数据库中。

检索:通过向量模型将用户的输入文本转换为向量 -> 在向量数据库中匹配这个用户输入对应的向量 -> 将匹配到的内容和用户的输入的文本都返回给大模型。

文件加载器(加载文件)

目前使用的是langchain4j的 FileSystemDocumentLoader。

对应的代码为下:

 @Test
    public void test3() {
        // 加载单个文档
        Document document0 = FileSystemDocumentLoader.loadDocument("D:\\桌面\\ai\\资料\\knowledge\\测试.txt", new
                TextDocumentParser());
        System.out.println(document0);
// 从一个目录中加载所有文档
        List<Document> documents1 = FileSystemDocumentLoader.loadDocuments("D:\\桌面\\ai\\资料\\knowledge", new
                TextDocumentParser());
        System.out.println(documents1);
// 从一个目录中加载所有的.txt文档
        PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
        List<Document> documents2 = FileSystemDocumentLoader.loadDocuments("D:\\桌面\\ai\\资料\\knowledge",
                pathMatcher, new TextDocumentParser());
        System.out.println(documents2);
// 从一个目录及其子目录中加载所有文档
        List<Document> documents3 =
                FileSystemDocumentLoader.loadDocumentsRecursively("D:\\桌面\\ai\\资料\\knowledge", new
                        TextDocumentParser());
        System.out.println(documents3);
    }

文件解析器(TextDocumentParser,解析文件)

解析对应的文件。(以pdf为例)

对应代码为下:
  依赖

<!--解析pdf文档-->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
            <version>1.2.0-beta8</version>
        </dependency>

  测试代码

    @Test
    public void test4() {
        Document document = FileSystemDocumentLoader.loadDocument(
                "E:/knowledge/医院信息.pdf",
                new ApachePdfBoxDocumentParser()
        );
        System.out.println(document);
    }

向量转换及向量存储

将文本数据转换为向量形式并存储到数据库中。

对应代码为下:

  依赖

<!--简单的rag实现-->
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
</dependency>

测试代码

    @Test
    public void testReadDocumentAndStore() {
//并使用默认的文档解析器对文档进行解析(TextDocumentParser)
        Document document = FileSystemDocumentLoader.loadDocument("D:\\桌面\\ai\\资料\\knowledge\\人工智能.md");
//将向量数据存储到你内存中
                InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>
                        ();
//1、分割文档:默认使用递归分割器ingest,将文档分割为多个文本片段,每个片段包含不超过 300个token,
//3、将原始文本和向量存储到向量数据库中(InMemoryEmbeddingStore)
        EmbeddingStoreIngestor.ingest(document, embeddingStore);
//查看向量数据库内容
        System.out.println(embeddingStore);
    }

结果:将当前到的document分为了17个向量数据存储到内存中。

文件分割器(分割文本)

将文本分割成多个小文本,为转换为向量做准备。

  • 按段落文档分割器(DocumentByParagraphSplitter)
  • 按行文档分割器(DocumentByLineSplitter
  • 按句子文档分割器(DocumentBySentenceSplitter
  • 按单词文档分割器(DocumentByWordSplitter
  • 按字符文档分割器(DocumentByCharacterSplitter
  • 按正则表达式文档分割器(DocumentByRegexSplitter
  • 递归分割:DocumentSplitters.recursive (...)
  • 默认情况下每个文本片段最多不能超过300token

对应代码为下:

/**
     * 文档分割
     */
    @Test
    public void testDocumentSplitter() {
        //使用FileSystemDocumentLoader读取指定目录下的知识库文档
//并使用默认的文档解析器对文档进行解析(TextDocumentParser)
        Document document = FileSystemDocumentLoader.loadDocument("D:\\桌面\\ai\\资料\\knowledge\\人工智能.md");
//为了简单起见,我们暂时使用基于内存的向量存储
                InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>
                        ();
//自定义文档分割器
//按段落分割文档:每个片段包含不超过 300个token,并且有 20个token的重叠部分保证连贯性
//注意:当段落长度总和小于设定的最大长度时,就不会有重叠的必要。
        DocumentByParagraphSplitter documentSplitter = new DocumentByParagraphSplitter(
                300,
                30,
//token分词器:按token计算
            new DocumentByLineSplitter(300, 20));
        IngestionResult ingest = EmbeddingStoreIngestor
                .builder()
                .embeddingStore(embeddingStore)
                .documentSplitter(documentSplitter)
                .build()
                .ingest(document);
        TokenUsage tokenUsage = ingest.tokenUsage();
        System.out.println(tokenUsage);
    }

内存向量存储demo

配置Bean

@Bean
    public ContentRetriever contentRetrieverXiaozhi() {
//使用FileSystemDocumentLoader读取指定目录下的知识库文档
//并使用默认的文档解析器对文档进行解析
        Document document1 = FileSystemDocumentLoader.loadDocument("D:\\桌面\\ai\\资料\\knowledge医院信息.md");
        Document document2 = FileSystemDocumentLoader.loadDocument("D:\\桌面\\ai\\资料\\knowledge科室信息.md");
        Document document3 = FileSystemDocumentLoader.loadDocument("D:\\桌面\\ai\\资料\\knowledge神经内科.md");
        List<Document> documents = Arrays.asList(document1, document2, document3);
//使用内存向量存储
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>
                ();
//使用默认的文档分割器
        EmbeddingStoreIngestor.ingest(documents, embeddingStore);
//从嵌入存储(EmbeddingStore)里检索和查询内容相关的信息
        return EmbeddingStoreContentRetriever.from(embeddingStore);
    }

AiService中配置对应的内存向量Bean

@AiService(wiringMode = EXPLICIT,
            chatModel = "qwenChatModel",
        chatMemoryProvider = "chatMemoryProvider",
        tools = {"toolConfig"},
        contentRetriever = "contentRetrieverXiaozhi"
)
public interface AIAssistant {
    @SystemMessage(fromResource = "template.txt")
    String chat(@MemoryId int memoryId, @UserMessage String userMessage);
}

修改用户Tool方法的描述

    @Tool(name = "预约挂号",value = "判断用户提供的身份证在redis的键值对中是否作为key存储过,如果没有数据就存一份预约数据到redis中,根据参数,让用户确认所有预约信息,用户确认后再进行预约。如果用户没有提供具体的医生姓名,请从向量存储中找到一位医生。")
    public String saveRecord(@P(value = "用户的名字", required = true) String name, @P("身份证") String idCard, @P("预约的科室") String room) {
        String data = (String) redisTemplate.opsForValue().get(idCard);
        if(data == null) {
            redisTemplate.opsForValue().set(idCard, Json.toJson(new Record(name, idCard, room)));
            return "预约成功";
        } else {
            return "已经有预约了";
        }
    }

进行测试(最终会到内存向量中查询教师的信息)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值