# LangChain4J

# 官网 (opens new window)

# 依赖

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

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-bom</artifactId>
            <version>1.0.0-beta3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

# hello world

  1. 配置文件
server.port=8080

langchain4j.open-ai.chat-model.base-url=http://langchain4j.dev/demo/openai/v1
langchain4j.open-ai.chat-model.api-key=demo
langchain4j.open-ai.chat-model.model-name=gpt-4o-mini

# 发送给大模型的请求日志和响应日志
langchain4j.open-ai.chat-model.log-requests=true
langchain4j.open-ai.chat-model.log-responses=true
#需要设置为debug才能看到日志
logging.level.root=debug
  1. 对话
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class LLMtest {

    @Test
    public void test() {
        OpenAiChatModel model = OpenAiChatModel.builder()
                .apiKey("demo")
                .modelName("gpt-4o-mini")
                .build();
        String hello = model.chat("你是什么模型");
        System.out.println(hello);
    }


    @Autowired
    private OpenAiChatModel openAiChatModel;

    @Test
    public void langChan4j_springboot() {
        String who = openAiChatModel.chat("你是什么模型");
        System.out.println(who);
    }
}

# 支持的模型

LangChain4j支持接入的大模型:https://docs.langchain4j.dev/integrations/language-models/

# ollama 依赖

不输入版本是因为上面引入的dev.langchain4j

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

# AI Service

  • AiServices会组装Assistant接口以及其他组件,并使用反射机制创建一个实现Assistant接口的代理对象。 这个代理对象会处理输入和输出的所有转换工作。在这个例子中,chat方法的输入是一个字符串,但是大模型需要一个UserMessage对象。所以,代理对象将这个字符串转换为UserMessage,并调用 聊天语言模型。chat方法的输出类型也是字符串,但是大模型返回的是AiMessage对象,代理对象会将其转换为字符串。
  • 简单理解就是:代理对象的作用是输入转换和输出转换

# 依赖

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

# 定义接口


import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;

//当有多个model的时候需要指定到底使用哪个
@AiService(chatModel = "openAiChatModel", wiringMode = AiServiceWiringMode.EXPLICIT)
public interface Assistant {
    String chat(String userMessage);
}

# 使用

DANGER

注意这里是需要配置文件的,配置文件和上面的 Hello World是一样的。其次这里可以使用 AIService.create创建代理也可以用注解

@SpringBootTest
@Slf4j
public class AIServiceTest {

    @Autowired
    private OpenAiChatModel openAiChatModel;

    //使用AIService
    @Test
    public void test() {
        Assistant assistant = AiServices.create(Assistant.class, openAiChatModel);
        String whoRes = assistant.chat("你是什么模型");
        log.info("whoRes:{}", whoRes);
    }


    @Autowired
    private Assistant assistant;

    @Test
    public void testAnnotationAIService() {
        String who = assistant.chat("你是谁");
        log.info("who:{}", who);
    }

}

# 聊天记忆

  • chatMemory 实现聊天记忆
    @Test
    public void recordMessageChatMemory() {
        //使用chatMemory聊天记忆
        //接受10条聊天记忆
        MessageWindowChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(10);
        Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(openAiChatModel).chatMemory(chatMemory).build();
        log.info("assistant res:{}", assistant.chat("我叫高亮"));
        log.info("assistant res:{}", assistant.chat("我叫什么"));

    }
  • 使用 AiService 注解实现
  1. 定义一个ChatMemory bean
@Configuration
public class ChatMemoryConfig {
    @Bean
    public ChatMemory myChatMemory() {
        return MessageWindowChatMemory.withMaxMessages(10);
    }
}
  1. 使用 AIService 注解,并使用刚刚配置的ChatMemory
@AiService(
        chatModel = "openAiChatModel",
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatMemory = "myChatMemory"
)
public interface ChatMemoryAssistant {
    String chat(String message);
}
  1. 使用
    @Autowired
    private ChatMemoryAssistant chatMemoryAssistant;

    @Test
    public void aiServiceChatMemory() {
        chatMemoryAssistant.chat("你好,我叫高大根");
        log.info("chatMemoryAssistant:{}", chatMemoryAssistant.chat("你好,我叫什么"));
    }

# 自定义会话记忆

  • 目前的两个默认实现都是存储在内存中的,可以将记忆保存到 Mysql 或者其他自己选择。
  • 需要实现 ChatMemoryStore 接口。
  • 将自定义的会话记忆实现类放入到配置中
@Configuration
public class ChatMemoryConfig {

    @Autowired
    private MongoInmemoryStore mongoInmemoryStore;

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

    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return id -> MessageWindowChatMemory
                .builder()
                .id(id)
                //这里是自定义的会话记忆实现类
                .chatMemoryStore(mongoInmemoryStore)
                .maxMessages(10).build();
    }

}

# 会话隔离

  1. 配置
@Configuration
public class ChatMemoryConfig {

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

    @Bean
    public ChatMemoryProvider chatMemoryProvider() {
        return id -> MessageWindowChatMemory.builder().id(id).maxMessages(10).build();
    }

}

  1. @MemoryId 和 @UserMessage
@AiService(
        chatModel = "openAiChatModel",
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatMemory = "myChatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatMemoryAssistant {
    //这里id也可以是int类型
    String chat(@MemoryId String id, @UserMessage String message);
}

  1. 使用
    @Autowired
    private SeparateChatMemoryAssistant separateChatMemoryAssistant;

    @Test
    public void aiServiceSeparateChatMemory() {
        separateChatMemoryAssistant.chat("123", "我叫权志龙");
        log.info("memory1 :{}", separateChatMemoryAssistant.chat("123", "我叫什么"));
        log.info("memory2 :{}", separateChatMemoryAssistant.chat("1235", "我叫什么"));

    }

# 提示词

  • 系统提示词:@SystemMessage
    1. 也可以用提示模板:fromResource
  • 用户提示词**(需要接口方法只能一个参数)**:@UserMessage
@AiService(
        chatModel = "openAiChatModel",
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatMemory = "myChatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatMemoryAssistant {
    @UserMessage("我是你的好朋友,请用上海话回复我")
    String chat(String message);
}
  • 指定参数名称:@v
@AiService(
        chatModel = "openAiChatModel",
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatMemory = "myChatMemory",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatMemoryAssistant {
    @UserMessage("我是你的好朋友,请用上海话回复我, {{message}}")
    String chat(@V("message") String message);
}

# Function Calling 函数调用

function calling 也叫 tools 工具

# @Tool

  • name(工具名称):工具的名称。如果未提供该字段,方法名会作为工具的名称。
  • value(工具描述):工具的描述信息。 根据工具的不同,即使没有任何描述,大语言模型可能也能很好地理解它(例如,add(a,b)就很直观),但通常最好提供清晰且有意义的名称和描述。
    这样,大语言模型就能获得更多信息,以决定是否调用给定的工具以及如何调用。

# @P

方法参数可以选择使用@P注解进行标注。 @P注解有两个字段:

  • value:参数的描述信息,这是必填字段。
  • required:表示该参数是否为必需项,默认值为true,此为可选字段。

# @ToolMemoryId:区分用户

@Component
public class CalculateTools {

    @Tool(name = "加法工具", value = "用于加法运算")
    public double sum(
            @ToolMemoryId String memoryId,
            @P(value = "加数1") double a,
            @P(value = "加数2") double b) {
        System.out.println("调用加法");
        return a + b;
    }

    @Tool(name = "平方根运算")
    public double squareRoot(double x) {
        System.out.println("调用平方根运算");
        return Math.sqrt(x);
    }
}

@AiService(
        chatModel = "openAiChatModel",
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatMemoryProvider = "chatMemoryProvider",
        tools = "calculateTools"
)
public interface AIChatService {
  //    @SystemMessage(fromResource = "prompt.txt")
  String chat(@MemoryId String memoryId, @UserMessage String question);

}

# RAG

# 常用方法

  • 全文(关键词)搜索。这种方法通过将问题和提示词中的关键词与知识库文档数据库进行匹配来搜索文档。根据这些关键词在每个文档中的出现频率和相关性对搜索结果进行排序。
  • 向量搜索,也被称为“语义搜索”。文本通过,入模型被转换为数字向量。然后,它根据查询向量与文档向量之间的余弦相似度或其他相似性/距离度量来查找和排序文档,从而捕捉更深层次的语义含义。
  • 混合搜索。结合多种搜索方法(例如,全文搜索+向量搜索)通常可以提高搜索的效果

# 文档解析器

  • 来自 langchain4j 模块的文本文档解析器(TextDocumentParser),它能够解析纯文本格式的文件(例如 TXT、HTML、MD 等)。
  • 来自 langchain4j-document-parser-apache-pdfbox 模块的 Apache PDFBox 文档解析器(ApachePdfBoxDocumentParser),它可以解析 PDF 文件。
  • 来自 langchain4j-document-parser-apache-poi 模块的 Apache POI 文档解析器(ApachePoiDocumentParser),它能够解析微软办公软件的文件格式(例如 DOC、DOCX、PPT、PPTX、XLS、XLSX 等)。
  • 来自 langchain4j-document-parser-apache-tika 模块的 Apache Tika 文档解析器(ApacheTikaDocumentParser),它可以自动检测并解析几乎所有现有的文件格式。

默认文件解析


import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.util.List;

@SpringBootTest
public class RAGTest {

    @Test
    public void testFileParse() {
        //FileSystemDocumentLoader读取指定目录下文件 使用默认的text解析其
        Document document = FileSystemDocumentLoader.loadDocument("E:\\ide\\idea\\workspace\\springai-langchan4j\\src\\main\\resources\\测试.txt", new TextDocumentParser());
        System.out.println(document.text());

        //从一个目录加载文档
        List<Document> documents = FileSystemDocumentLoader.loadDocuments("E:\\ide\\idea\\workspace\\springai-langchan4j\\src\\main\\resources\\");
        System.out.println(document);

        // 从一个目录中加载所有的.txt文档
        PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
        List<Document> documents2 = FileSystemDocumentLoader.loadDocuments("E:\\install\\baiduspace\\资料\\knowledge", pathMatcher, new TextDocumentParser());
        System.out.println(documents2);
    }

}

# 文档分割器 Document Splitter

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