AI Services:多个 AI 服务串联

大模型应用的复杂度越高,就越需要拆解为细小模块,这与传统软件开发的设计思想是一致的。

例如,若试图在系统提示词中塞入海量指令来覆盖所有场景,不仅容易出错,效率也极低。指令过多时,大语言模型(LLMs)反而可能忽略部分关键内容;再加上指令的先后顺序同样会影响效果,这会让整体开发难度显著上升。

这一拆分原则同样适用于工具调用、检索增强生成(RAG)以及模型参数(如 temperature、maxTokens 等)的设计。

你的聊天机器人并不需要时刻调用全部可用工具。比如,当用户仅进行问候或告别时,让大语言模型访问数十甚至上百个工具,不仅成本高昂(每次工具调用都会消耗大量 Token),还可能带来风险 —— 模型可能出现幻觉,或被诱导以非预期的方式调用工具,产生不可控的结果。

对于检索增强生成(RAG)同理:在必要时为模型补充上下文是合理的,但不应无差别使用。因为上下文越长,Token 消耗越多,响应延迟也会越高,带来额外成本与性能损耗。

对于模型参数:当你需要输出稳定、可控时,会使用较低的 temperature;在需要创意、多样性时,则会调高 temperature。不同场景应使用不同配置。

核心结论是:更小、更专一的组件,更容易开发、测试、维护与理解,整体成本也更低。

在设计时,还需要思考两个极端方向:

  • 你希望应用高度确定,由程序控制整体流程,大语言模型仅作为其中一个组件?

  • 还是希望大语言模型拥有完全自主权,主导整个应用的逻辑?

  • 或是根据场景,采用两者混合的模式?

当你把应用拆分成更小、更易管理的模块时,以上所有架构模式都能轻松实现。

AI 服务可以像传统确定性组件一样使用,并与常规代码无缝结合:

  • 可以依次调用多个 AI 服务(即链式调用)。

  • 可以使用由 LLM 输出结果驱动的 if/else 逻辑(AI 可返回布尔值)。

  • 可以使用由 LLM 驱动的 switch 分支(AI 可返回枚举值)。

  • 可以使用由 LLM 驱动的 for/while 循环(AI 可返回整数等数值类型)。

  • 可以在单元测试中对 AI 服务进行 Mock 模拟(因为它是标准接口)。

  • 可以对每个 AI 服务单独做集成测试。

  • 可以分别评估每个 AI 服务,独立调优最优参数。

  • 等等。

举一个简单的例子:我想为公司搭建一个聊天机器人。

  • 如果用户只是打招呼,希望用预设话术回复,不依赖 LLM 生成。

  • 如果用户提出问题,再让 LLM 结合企业内部知识库(即 RAG)进行回答。

我们可以把这个需求拆分成两个独立的 AI 服务,具体实现思路如下:

// 定义业务接口
interface GreetingExpert {
    @UserMessage("以下文本是问候语吗?文本:{{it}}")
    boolean isGreeting(String text);
}

interface ChatBot {
    @SystemMessage("你是公司的礼貌聊天机器人。")
    String reply(String userMessage);
}


// 创建 AI 服务
private final GreetingExpert greetingExpert;
private final ChatBot chatBot;

public MultipleAiServicesDemo() {
    // 判断是否为问候语的模型
    ChatModel greetingExpertModel = OpenAiChatModel.builder()
            .baseUrl("https://api.xty.app/v1")
            .apiKey(API_KEY)
            .modelName("gpt-3.5-turbo")
            .logRequests(true)
            .logResponses(true)
            .build();
    greetingExpert = AiServices.builder(GreetingExpert.class)
            .chatModel(greetingExpertModel)
            .build();

    // 创建聊天模型
    ChatModel chatModel = OpenAiChatModel.builder()
            .baseUrl("https://api.xty.app/v1")
            .apiKey(API_KEY)
            .modelName("gpt-4")
            .logRequests(true)
            .logResponses(true)
            .build();
    chatBot = AiServices.builder(ChatBot.class)
            .chatModel(chatModel)
            .contentRetriever(createContentRetriever())
            .build();
}


// 核心处理逻辑
public String handle(String userMessage) {
    // 判断是否为问候语
    if (greetingExpert.isGreeting(userMessage)) {
        return "来公司的的问候!我能怎样让你的一天更美好呢?";
    } else {
        // 不是问候语,采用大模型进行回复
        return chatBot.reply(userMessage);
    }
}

注意,我们如何将更便宜的 gpt-3.5-turbo 用于识别文本是否是问候语的简单任务,而将更昂贵的带有内容检索器(RAG)的 GPT-4 用于更复杂的任务。

现在,我可以模拟 GreetingExpert 和 ChatBot,并独立测试 MultipleAiServicesDemo。此外,我还可以分别对 GreetingExpert 和 ChatBot 进行集成测试。我可以分别评估它们两者,为每个子任务找到最优参数,或者从长远来看,甚至可以为每个特定子任务微调一个小型的专用模型。

完整示例

下面是上面实现的完整示例:

package com.hxstrive.langchain4j.aiServices;

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import java.util.List;
import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocuments;

public class MultipleAiServicesDemo {
    // 推荐:将OPEN_API_KEY设置成环境变量, 避免硬编码或随着代码泄露
    // 注意,设置完环境变量记得重启IDEA,不然可能环境变量不会生效
    private static final String API_KEY = System.getenv("OPEN_API_KEY");

    // 定义业务接口
    interface GreetingExpert {
        @UserMessage("以下文本是问候语吗?文本:{{it}}")
        boolean isGreeting(String text);
    }

    interface ChatBot {
        @SystemMessage("你是公司的礼貌聊天机器人。")
        String reply(String userMessage);
    }

    // 创建服务实例
    private final GreetingExpert greetingExpert;
    private final ChatBot chatBot;

    public MultipleAiServicesDemo() {
        // 判断是否为问候语的模型
        ChatModel greetingExpertModel = OpenAiChatModel.builder()
                .baseUrl("https://api.xty.app/v1")
                .apiKey(API_KEY)
                .modelName("gpt-3.5-turbo")
                .logRequests(true)
                .logResponses(true)
                .build();
        greetingExpert = AiServices.builder(GreetingExpert.class)
                .chatModel(greetingExpertModel)
                .build();

        // 创建聊天模型
        ChatModel chatModel = OpenAiChatModel.builder()
                .baseUrl("https://api.xty.app/v1")
                .apiKey(API_KEY)
                .modelName("gpt-4")
                .logRequests(true)
                .logResponses(true)
                .build();
        chatBot = AiServices.builder(ChatBot.class)
                .chatModel(chatModel)
                .contentRetriever(createContentRetriever())
                .build();
    }

    /**
     * 创建文档内容检索器(RAG的核心依赖)
     * @return 可用于检索的ContentRetriever实例
     */
    private ContentRetriever createContentRetriever() {
        // 第一步:加载用于RAG的本地文档(支持多个txt文件)
        List<Document> documents = loadDocuments("E:\\~work_demo\\langchain4j\\document");

        // 创建空的内存向量库(存储文档文本片段及其向量表示,程序退出后数据丢失)
        InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

        // 创建嵌入模型
        OpenAiEmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
                .baseUrl("https://api.xty.app/v1")
                .apiKey(API_KEY)
                .modelName("text-embedding-3-small")
                .build();

        // 配置文档拆分器
        // 按固定长度拆分:每个片段200个字符,重叠20个字符(保证上下文连贯)
        DocumentSplitter documentSplitter = DocumentSplitters.recursive(200, 20);

        // 将文档导入向量库(底层自动完成:文档拆分 → 文本向量化 → 存入向量库)
        // 注:langchain4j 封装了所有细节,无需手动处理拆分/向量化
        EmbeddingStoreIngestor.builder()
                .embeddingModel(embeddingModel) // 设置嵌入模型
                .embeddingStore(embeddingStore) // 设置向量库,这里使用内存向量库
                .documentSplitter(documentSplitter) // 配置文档拆分器
                .build().ingest(documents);

        // 基于向量库创建检索器(用于后续提问时匹配相关文档片段)
        return EmbeddingStoreContentRetriever.builder()
                .embeddingModel(embeddingModel) // 嵌入模型,用于匹配文档片段,将用户问题向量化
                .embeddingStore(embeddingStore) // 设置向量库,上面我们已经处理了的文档
                .maxResults(3) // 只召回最相关的3个段落(可根据需求调整)
                .minScore(0.7) // 相似度阈值 ≥ 0.7 才召回(0-1之间,越高越精准)
                .build();
    }

    public String handle(String userMessage) {
        if (greetingExpert.isGreeting(userMessage)) {
            return "来公司的的问候!我能怎样让你的一天更美好呢?";
        } else {
            return chatBot.reply(userMessage);
        }
    }

    public static void main(String[] args) {
        MultipleAiServicesDemo milesOfSmiles = new MultipleAiServicesDemo();

        // 发起问候
        String greeting = milesOfSmiles.handle("Hello");
        System.out.println(greeting);

        // 聊天
        String answer = milesOfSmiles.handle("能简单说说随心变色手机壳产品吗?");
        System.out.println(answer);
    }

}

运行示例,输出日志如下:

# 对文档进行嵌入
16:59:20.138 [main] DEBUG dev.langchain4j.store.embedding.EmbeddingStoreIngestor -- Starting to ingest 2 documents
16:59:20.180 [main] DEBUG dev.langchain4j.store.embedding.EmbeddingStoreIngestor -- Documents were split into 40 text segments
16:59:20.180 [main] DEBUG dev.langchain4j.store.embedding.EmbeddingStoreIngestor -- Starting to embed 40 text segments
16:59:35.241 [main] DEBUG dev.langchain4j.store.embedding.EmbeddingStoreIngestor -- Finished embedding 40 text segments
16:59:35.241 [main] DEBUG dev.langchain4j.store.embedding.EmbeddingStoreIngestor -- Starting to store 40 text segments into the embedding store
16:59:35.243 [main] DEBUG dev.langchain4j.store.embedding.EmbeddingStoreIngestor -- Finished storing 40 text segments into the embedding store


# 判断是否为问候语
16:59:35.337 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], ...
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "以下文本是问候语吗?文本:Hello\nYou must answer strictly in the following format: one of [true, false]"
  } ],
  "stream" : false
}


# 响应,返回true,是问候语
16:59:39.176 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP response:
- status code: 200
- headers: [:status: 200], [access-control-allow-headers: *], ...
- body: {
    "id": "chatcmpl-HqwfH4IJkP4Sb9oaQzC0439maEtpI",
    "object": "chat.completion",
    "created": 1770800378,
    "model": "gpt-3.5-turbo-0613",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "true" // 返回 true
            },
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 39,
        "completion_tokens": 1,
        "total_tokens": 40
    },
    "system_fingerprint": "fp_b28b39ffa8"
}


# 直接返回问候语
来公司的的问候!我能怎样让你的一天更美好呢?


# 第二次提问
16:59:39.202 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], ...
- body: {
  "model" : "gpt-3.5-turbo",
  "messages" : [ {
    "role" : "user",
    "content" : "以下文本是问候语吗?文本:能简单说说随心变色手机壳产品吗?\nYou must answer strictly in the following format: one of [true, false]"
  } ],
  "stream" : false
}

# 第二次响应
16:59:42.250 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP response:
- status code: 200
- headers: [:status: 200], ...
- body: {
    "id": "chatcmpl-JZsw2XSmkDuKyGlvOIRUh9KMAWJi7",
    "object": "chat.completion",
    "created": 1770800381,
    "model": "gpt-3.5-turbo-0613",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "false" // 不是问候语
            },
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 54,
        "completion_tokens": 1,
        "total_tokens": 55
    },
    "system_fingerprint": "fp_b28b39ffa8"
}


# 不是问候语,发起大模型问答,同时有部分 RAG 信息召回
16:59:43.764 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP request:
- method: POST
- url: https://api.xty.app/v1/chat/completions
- headers: [Authorization: Beare...00], [User-Agent: langchain4j-openai], [Content-Type: application/json]
- body: {
  "model" : "gpt-4",
  "messages" : [ {
    "role" : "system",
    "content" : "你是公司的礼貌聊天机器人。"
  }, {
    "role" : "user",
    "content" : "能简单说说随心变色手机壳产品吗?\n\nAnswer using the following information:\n随心变色手机壳产品说明书\n一、产品简介\n\n随心变色手机壳产品说明书\n\n随心变色手机壳产品说明书 一、产品简介 本产品为随心变色手机壳,核心功能为根据用户心情自动切换颜色,无需手动操作,使用便捷。外壳采用高分子耐磨材料与食品级记忆橡胶精心打磨制成,质感细腻亲肤,防刮耐摔且韧性出众,无需充电即可实现智能变色功能,广泛适配苹果(iPhone 12-15系列)、小米(小米14系列、Redmi K70系列)、华为(Mate 60系列、Pura"
  } ],
  "stream" : false
}


# 回复
16:59:51.441 [main] INFO dev.langchain4j.http.client.log.LoggingHttpClient -- HTTP response:
- status code: 200
- headers: [:status: 200], ...
- body: {
    "id": "chatcmpl-D80MSHq1A16DQx1opIFHsPAzAgBYF",
    "object": "chat.completion",
    "created": 1770800384,
    "model": "gpt-4",
    "choices": [
        {
            "index": 0,
            "message": {
                "role": "assistant",
                "content": "随心变色手机壳是一款智能科技与高品质材质相结合的创新产品,其核心功能是能够根据用户心情自动切换颜色,无需手动操作,使用非常便捷。手机壳采用高分子耐磨材料和食品级记忆橡胶制成,触感细腻亲肤,同时具备防刮、耐摔和优异的韧性特点。另外,这款手机壳无需充电也能实现智能变色功能,兼顾便利性与环保性能。目前支持多种热门手机型号,包括苹果(iPhone 12-15系列)、小米(小米14系列、Redmi K70系列)和华为(Mate 60系列)等机型,非常适配广泛用户需求。",
                "refusal": null,
                "annotations": []
            },
            "logprobs": null,
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 206,
        "completion_tokens": 168,
        "total_tokens": 374,
        "prompt_tokens_details": {
            "cached_tokens": 0,
            "audio_tokens": 0
        },
        "completion_tokens_details": {
            "reasoning_tokens": 0,
            "audio_tokens": 0,
            "accepted_prediction_tokens": 0,
            "rejected_prediction_tokens": 0
        }
    },
    "system_fingerprint": "fp_b54fe76834"
}

随心变色手机壳是一款智能科技与高品质材质相结合的创新产品,其核心功能是能够根据用户心情自动切换颜色,无需手动操作,使用非常便捷。手机壳采用高分子耐磨材料和食品级记忆橡胶制成,触感细腻亲肤,同时具备防刮、耐摔和优异的韧性特点。另外,这款手机壳无需充电也能实现智能变色功能,兼顾便利性与环保性能。目前支持多种热门手机型号,包括苹果(iPhone 12-15系列)、小米(小米14系列、Redmi K70系列)和华为(Mate 60系列)等机型,非常适配广泛用户需求。

更多 LangChain4j 知识请阅读后续教程……

  

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
其他应用
公众号