1. 为什么需要混合部署?本地与云端模型的黄金搭档

大家好,我是老张,在AI和智能硬件这块摸爬滚打了十来年。今天想和大家聊聊一个非常“接地气”的场景:当你既想享受本地大模型的隐私、零延迟和低成本,又馋云端大模型那强大的能力和丰富的选择时,该怎么办?答案就是——混合部署。而LangChain4j,正是帮你把这两者无缝粘合起来的“超级胶水”。

我遇到过不少开发者朋友,他们的情况很典型:公司内部有些敏感数据,比如客户信息、内部文档,肯定不能直接丢到公网API去处理,用本地模型最放心。但本地机器性能有限,跑不动千亿参数的大模型,遇到需要复杂推理、代码生成或者多轮深度对话的任务时,本地小模型就显得力不从心了。这时候,如果有一个框架能让我在代码里轻松决定:“这段对话用我本地的Qwen2.5-7B处理”, “那个复杂的分析任务调用阿里百炼上的Qwen-Max”,那开发效率和系统能力都能得到质的提升。

LangChain4j就是这个问题的完美解决方案。它是一个专门为Java和JVM生态(比如Kotlin, Scala)设计的AI应用框架。它的核心思想是“编排”,把大模型调用、工具使用、记忆管理、数据检索这些复杂环节,像搭积木一样组合起来。对于混合部署场景,它提供了统一的接口,让你用几乎相同的代码风格去调用本地Ollama服务和云端阿里百炼API,大大降低了开发和维护的复杂度。

简单来说,这套组合拳能帮你:

  • 成本可控:日常的、简单的、对隐私要求高的任务走本地,省钱又安全。
  • 能力弹性伸缩:遇到高难度任务,一键切换云端顶级模型,瞬间获得“超能力”。
  • 架构统一:一套代码管理所有模型调用,不用为每个服务写不同的客户端代码。
  • 快速实验:可以非常方便地对比同一个问题,本地模型和云端模型的回答差异,帮你做技术选型。

接下来,我就手把手带你从零开始,搭建这个混合部署的环境,并用LangChain4j写一个能智能路由的小应用。

2. 搭建你的双模型基地:Ollama本地与阿里百炼云端

工欲善其事,必先利其器。混合部署的第一步,就是把两边的模型环境都准备好。这一部分我们会分头行动,把两个“基地”都搭建起来。

2.1 在本地轻松玩转Ollama

Ollama可以说是目前最受欢迎的本地大模型运行工具了,它把复杂的模型部署简化成了几条命令,对新手极其友好。

第一步:安装与初体验 直接访问Ollama官网,下载对应你操作系统(Windows/macOS/Linux)的安装包。安装过程就是一路“下一步”,非常简单。安装完成后,打开你的终端(或命令提示符/PowerShell),你会看到欢迎信息。它通常会提示你运行一个类似 ollama run llama3.2 的命令来拉取第一个模型。Llama3.2是一个不错的入门选择,但如果你网络不太好,或者想更快上手,我建议先跑一个超小模型试试水:

ollama run qwen2.5:0.5b

这个命令会从Ollama的模型库中下载一个只有5亿参数的Qwen2.5迷你版。下载完成后,你会直接进入一个交互式聊天界面,就像在终端里和AI对话一样,输入 bye 退出。这证明你的Ollama服务已经成功跑起来了,它会在后台启动一个服务,默认监听 11434 端口。

第二步:管理你的本地模型库 你不可能只用一个模型。Ollama支持很多模型,比如DeepSeek、Mistral、Gemma等。查看所有可用模型,可以去Ollama官网的模型库页面。在终端里,你可以用以下命令管理模型:

# 查看本地已下载的模型列表
ollama list

# 拉取一个新的模型(例如,一个7B参数的中等规模模型)
ollama pull qwen2.5:7b

# 删除一个本地模型(谨慎操作)
ollama rm qwen2.5:0.5b

我个人的经验是,在开发机上常备一个7B参数左右的模型(如 qwen2.5:7b 或 llama3.2:7b),它在速度和能力上有一个很好的平衡,足够处理很多本地化任务。

第三步:验证API调用 Ollama不仅提供命令行聊天,更提供了标准的HTTP API,这是我们用LangChain4j集成它的关键。我们来做个快速测试,确保API是通的。在终端里新开一个窗口,用 curl 命令(或者你喜欢的API测试工具如Apifox、Postman)发送一个请求:

curl http://localhost:11434/api/chat -d '{
  "model": "qwen2.5:7b",
  "messages": [
    { "role": "user", "content": "用一句话介绍下你自己" }
  ],
  "stream": false
}'

如果看到返回了一个包含AI回答的JSON数据,那么恭喜你,本地模型基地已经就绪!这个 http://localhost:11434/v1 就是后续我们要告诉LangChain4j的“地址”。

2.2 获取阿里百炼的云端力量

阿里百炼是阿里云推出的一站式大模型服务平台,上面集成了通义千问系列等多种强大的模型。对于需要顶级AI能力的场景,它是我们的不二之选。

第一步:开通服务与获取密钥

  1. 访问阿里云百炼平台,用你的阿里云账号登录。
  2. 首次进入,平台通常会引导你“开通服务”并领取免费额度(新用户都有,足够我们做大量实验)。务必领取,这是真金白银的抵扣。
  3. 开通后,点击顶部导航栏的“模型”,在左侧菜单找到“API-KEY管理”。
  4. 创建一个新的API-KEY,并立即妥善保存。这个KEY只会显示一次,就像密码一样,丢失了只能重新创建。

注意:这个API-KEY是你的云端模型调用凭证,千万不要泄露或提交到公开的代码仓库(如GitHub)。后续我们会用环境变量来管理它。

第二步:找到模型的调用方式 在“模型”页面,你可以看到琳琅满目的模型,从轻量版的Qwen-Long到功能强大的Qwen-Max。点击任意一个模型卡片,进入详情页,你会看到一个“API参考”或“调用指南”的标签页。这里详细列出了该模型的接入地址、请求格式和参数说明。

关键信息是这两个:

  • API端点:通常是 https://dashscope.aliyuncs.com/compatible-mode/v1 (这是兼容OpenAI格式的通用端点,强烈推荐使用,因为LangChain4j对OpenAI协议支持最好)。
  • API-KEY:就是你上一步创建的那个。

第三步:快速云端测试 同样,我们可以在Apifox里快速测试一下。创建一个POST请求:

  • URL: https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions
  • Header:
    • Authorization: Bearer YOUR_API_KEY (将YOUR_API_KEY替换成你的真实Key)
    • Content-Type: application/json
  • Body (JSON):
{
  "model": "qwen-plus",
  "messages": [
    {"role": "user", "content": "你好,请写一首关于春天的五言绝句。"}
  ]
}

发送请求,你应该能立刻收到来自云端强大模型的诗作。这说明你的云端通道也打通了。

至此,你的“双模型基地”已经全部搭建完成。一边是部署在自己电脑上、听话又隐私的本地模型,另一边是拥有“洪荒之力”的云端模型API。接下来,就是请出主角LangChain4j,让它来指挥这两支“部队”协同作战。

3. 引入LangChain4j:构建统一的模型调用层

现在,我们有了本地和云端两个模型服务。如果每个服务都写一套不同的HTTP客户端代码,那会非常混乱。LangChain4j的核心价值之一,就是提供了一层抽象,让我们能用统一的方式和任何模型对话。这一章,我们来创建项目,并引入必要的依赖。

我假设你使用Maven来管理Java项目(Gradle的配置逻辑类似)。在你的 pom.xml 文件中,需要添加以下关键依赖:

<dependencies>
    <!-- LangChain4j 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j</artifactId>
        <version>0.31.0</version> <!-- 请使用最新稳定版 -->
    </dependency>
    <!-- LangChain4j 用于 OpenAI 兼容 API 的模块 -->
    <!-- 阿里百炼的兼容端点,以及Ollama的OpenAI格式端点,都用这个 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-open-ai</artifactId>
        <version>0.31.0</version>
    </dependency>
    <!-- 如果你需要更直接地使用Ollama的原生API(非必须,OpenAI兼容方式更简单) -->
    <!-- <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-ollama</artifactId>
        <version>0.31.0</version>
    </dependency> -->
    <!-- 一个简单的日志框架,方便查看内部调用情况 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>2.0.9</version>
    </dependency>
</dependencies>

这里我强烈推荐使用 langchain4j-open-ai 模块来对接两者。为什么呢?因为Ollama从某个版本开始,也提供了一个完全兼容OpenAI API协议的端点(http://localhost:11434/v1),而阿里百炼也提供了兼容端点。这意味着我们可以用同一套代码、同一种配置风格来初始化两个模型的客户端,只是传入的 baseUrl 和 apiKey 不同而已,极大地简化了开发。

如果你发现你的Ollama版本较旧,没有 /v1 端点,那么你可以使用 langchain4j-ollama 模块,它是专门为Ollama原生API封装的。但在本文中,为了保持统一和简洁,我们默认使用OpenAI兼容模式,这是目前最主流、最推荐的方式。

依赖加好后,我们就可以开始编写代码了。我会先分别展示如何创建本地和云端的客户端,你会惊讶于它们的相似性。

4. 双剑合璧:编写LangChain4j集成代码

代码部分是我们实战的核心。我会带你一步步创建两个模型客户端,并演示一个简单的“智能路由”示例,让你感受混合调度的魅力。

4.1 初始化本地Ollama客户端

首先,我们来连接本地的Ollama服务。记得确保你的Ollama服务正在运行(ollama serve 或直接运行过模型)。

import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;

public class HybridModelDemo {

    public static void main(String[] args) {
        // 1. 创建本地Ollama客户端 (通过OpenAI兼容接口)
        ChatLanguageModel localModel = OpenAiChatModel.builder()
                .baseUrl("http://localhost:11434/v1") // Ollama的OpenAI兼容端点
                .apiKey("ollama") // Ollama在此模式下不需要真实的key,但需要填一个非空值
                .modelName("qwen2.5:7b") // 指定你要使用的本地模型名称
                .temperature(0.7) // 创造性,0-1之间,值越高回答越随机
                .logRequests(true) // 开启请求日志,调试时非常有用
                .logResponses(true)
                .build();

        System.out.println("=== 测试本地模型 (Qwen2.5-7B) ===");
        String localAnswer = localModel.generate("请用简短的话解释什么是人工智能?");
        System.out.println("本地模型回答: " + localAnswer);
    }
}

看,代码非常简单!我们使用了 OpenAiChatModel 这个构建器,只是把 baseUrl 指向了本地的 11434 端口下的 /v1 路径。apiKey 随便填个字符串(如"ollama")即可,因为本地服务不需要认证。modelName 必须和你用 ollama list 看到的名称完全一致。

运行这段代码,你应该能看到控制台打印出请求和响应的日志,以及本地模型对你问题的回答。这证明LangChain4j已经成功接管了与Ollama的通信。

4.2 初始化阿里百炼云端客户端

接下来,我们用几乎一模一样的方式来初始化云端客户端。唯一的区别就是 baseUrl 和 apiKey。

// 2. 创建阿里百炼云端客户端
// 重要:建议从环境变量读取API Key,避免硬编码在代码中
String cloudApiKey = System.getenv("ALIYUN_BAILIAN_API_KEY");
if (cloudApiKey == null || cloudApiKey.isEmpty()) {
    System.err.println("请设置环境变量 ALIYUN_BAILIAN_API_KEY");
    return;
}

ChatLanguageModel cloudModel = OpenAiChatModel.builder()
        .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1") // 百炼的兼容端点
        .apiKey(cloudApiKey) // 使用从环境变量获取的真实Key
        .modelName("qwen-plus") // 指定云端模型,例如 qwen-plus, qwen-max
        .temperature(0.3) // 对云端模型,我通常设低一点,让回答更确定、更精准
        .logRequests(true)
        .logResponses(true)
        .build();

System.out.println("\n=== 测试云端模型 (Qwen-Plus) ===");
String cloudAnswer = cloudModel.generate("请用简短的话解释什么是人工智能?");
System.out.println("云端模型回答: " + cloudAnswer);

这里有几个关键点:

  1. API Key安全:我强烈建议通过环境变量来传递云端的API Key。你可以在运行程序前,在终端执行 export ALIYUN_BAILIAN_API_KEY=your_key_here (Linux/macOS) 或 set ALIYUN_BAILIAN_API_KEY=your_key_here (Windows)。
  2. 模型选择:modelName 这里填的是你在百炼平台上看到的模型名称,如 qwen-plus、qwen-max、qwen-long 等。
  3. 参数微调:我故意为云端模型设置了较低的 temperature (0.3)。对于需要严谨、准确答案的任务(比如分析、总结),低温度值能让模型更专注于事实,减少“胡言乱语”。而对于创意写作,本地模型可以设置高一点(如0.8)。

分别运行这两段测试代码,你会得到两个模型对同一个问题的回答。可以直观地感受一下,7B的本地模型回答通常更直接、快速,而云端大模型的回答往往更详尽、逻辑性更强、文采也可能更好。

4.3 实现一个简单的智能路由策略

分别调用只是第一步,真正的威力在于“智能调度”。我们来写一个简单的路由逻辑:根据用户问题的复杂程度,决定派给本地模型还是云端模型。

一个非常简单的策略是:根据问题的长度或关键词来判断。在实际项目中,你可以用更复杂的分类器(甚至用一个小模型)来做决策。

public class SmartRouter {
    private final ChatLanguageModel localModel;
    private final ChatLanguageModel cloudModel;

    public SmartRouter(ChatLanguageModel localModel, ChatLanguageModel cloudModel) {
        this.localModel = localModel;
        this.cloudModel = cloudModel;
    }

    public String routeAndAnswer(String userQuestion) {
        // 策略1:基于问题长度和关键词的简单路由
        boolean shouldUseCloud = isComplexQuestion(userQuestion);

        ChatLanguageModel modelToUse = shouldUseCloud ? cloudModel : localModel;
        String modelName = shouldUseCloud ? "云端(Qwen-Plus)" : "本地(Qwen2.5-7B)";

        System.out.printf("[路由决策] 问题: \"%s\" -> 使用模型: %s%n",
                userQuestion.length() > 50 ? userQuestion.substring(0, 50) + "..." : userQuestion,
                modelName);

        // 调用选中的模型生成答案
        return modelToUse.generate(userQuestion);
    }

    private boolean isComplexQuestion(String question) {
        question = question.toLowerCase();
        // 定义一些我们认为“复杂”的关键词或条件
        boolean isLong = question.length() > 100; // 长问题可能更复杂
        boolean hasComplexKeywords = question.contains("解释") ||
                                     question.contains("分析") ||
                                     question.contains("对比") ||
                                     question.contains("创作") ||
                                     question.contains("代码");
        // 简单问题关键词
        boolean isSimpleGreeting = question.matches("(你好|hello|hi|早上好).*");
        boolean isSimpleFact = question.contains("是什么") && question.length() < 30;

        // 决策逻辑:长问题或有关键词,且不是简单问候/事实,则用云端
        return (isLong || hasComplexKeywords) && !(isSimpleGreeting || isSimpleFact);
    }

    public static void main(String[] args) {
        // 初始化两个模型(代码同上,此处省略)
        ChatLanguageModel localModel = ...;
        ChatLanguageModel cloudModel = ...;

        SmartRouter router = new SmartRouter(localModel, cloudModel);

        String[] testQuestions = {
                "你好,今天天气怎么样?", // 简单问候,应路由到本地
                "Java和Python的主要区别是什么?", // 对比问题,应路由到云端
                "写一个简单的Java函数计算斐波那契数列。", // 代码生成,应路由到云端
                "人工智能的定义?" // 简短事实,可路由到本地
        };

        for (String q : testQuestions) {
            System.out.println("\n>>> 用户提问: " + q);
            String answer = router.routeAndAnswer(q);
            System.out.println("<<< AI回答: " + answer);
            System.out.println("---");
        }
    }
}

在这个例子中,isComplexQuestion 方法实现了一个非常基础的决策逻辑。运行这个程序,你会看到控制台清晰地打印出每个问题被路由到了哪个模型,并给出相应的回答。虽然这个路由策略很原始,但它清晰地展示了混合架构的核心思想:让合适的模型处理合适的问题。

5. 进阶实战:构建一个带记忆的对话助手

前面的例子都是单次问答。在实际应用中,对话往往是有上下文、有记忆的。LangChain4j另一个强大的功能就是轻松管理对话历史。我们来构建一个更真实的、能记住上下文的对话助手,并且它依然能在本地和云端模型间智能切换。

我们将使用 ConversationalChain 和 MessageWindowChatMemory 来实现。

import dev.langchain4j.chain.ConversationalChain;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.openai.OpenAiChatModel;

public class ConversationalAssistant {
    private final ConversationalChain localChain;
    private final ConversationalChain cloudChain;
    private final SmartRouter router; // 复用之前的路由器

    public ConversationalAssistant() {
        // 初始化模型(略,同前)
        ChatLanguageModel localModel = OpenAiChatModel.builder()...build();
        ChatLanguageModel cloudModel = OpenAiChatModel.builder()...build();

        // 为每个模型创建独立的对话链和记忆
        // MessageWindowChatMemory会保留最近N条消息作为上下文
        this.localChain = ConversationalChain.builder()
                .chatLanguageModel(localModel)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // 记住最近10轮对话
                .build();
        this.cloudChain = ConversationalChain.builder()
                .chatLanguageModel(cloudModel)
                .chatMemory(MessageWindowChatMemory.withMaxMessages(10))
                .build();

        this.router = new SmartRouter(localModel, cloudModel); // 假设SmartRouter稍作修改,能返回模型引用
    }

    public String chat(String userMessage) {
        // 1. 根据当前用户消息决定本次使用哪个模型
        boolean useCloud = router.isComplexQuestion(userMessage);
        ConversationalChain currentChain = useCloud ? cloudChain : localChain;

        // 2. 执行对话链。链会自动处理:将用户消息+历史记忆 -> 发给模型 -> 返回AI回复 -> 更新记忆
        String aiResponse = currentChain.execute(userMessage);

        // 3. (可选)记录日志
        System.out.printf("[记忆对话] 模型: %s, 用户: %s, AI: %s%n",
                useCloud ? "云端" : "本地",
                userMessage,
                aiResponse.length() > 60 ? aiResponse.substring(0, 60) + "..." : aiResponse);
        return aiResponse;
    }

    public static void main(String[] args) {
        ConversationalAssistant assistant = new ConversationalAssistant();

        // 模拟多轮对话
        System.out.println("助手: 你好!我是你的混合AI助手。简单问题我会用本地模型快速回答,复杂问题我会调用云端强大模型。");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("\n你: ");
            String input = scanner.nextLine();
            if ("exit".equalsIgnoreCase(input) || "退出".equalsIgnoreCase(input)) {
                break;
            }
            String response = assistant.chat(input);
            System.out.println("助手: " + response);
        }
        scanner.close();
        System.out.println("对话结束。");
    }
}

这个助手类更贴近真实应用了。它维护了两套独立的对话链和记忆系统。当你问“今天的天气怎么样?”时,它可能用本地模型回答,并且这个问答记录在本地模型的记忆里。紧接着你问“那明天呢?”,由于上下文存在,本地模型能理解“明天”指的是天气,并给出连贯的回答。而当你突然问“请分析一下全球气候变化对农业的长期影响”时,路由器会判断这是复杂问题,转而使用云端模型链,云端模型也会基于它自己的记忆上下文(如果有的话)来回答。

这种设计的好处是隔离性很好,本地和云端的对话上下文互不干扰。当然,你也可以设计更复杂的记忆共享策略,这取决于你的业务需求。

6. 避坑指南与性能优化建议

踩过不少坑之后,我总结了一些在混合部署中使用LangChain4j、Ollama和阿里百炼的关键要点,希望能帮你少走弯路。

1. 网络与超时设置

  • 本地Ollama:确保 localhost:11434 可访问。如果服务启动失败,检查Ollama进程。在Docker或远程服务器部署时,注意IP地址要改。
  • 阿里百炼:国内访问稳定,但也要考虑网络波动。务必在构建客户端时设置合理的超时,避免线程长时间阻塞。
    OpenAiChatModel.builder()
        .baseUrl("...")
        .apiKey("...")
        .timeout(Duration.ofSeconds(60)) // 设置完整的调用超时
        .connectTimeout(Duration.ofSeconds(10)) // 连接超时
        .readTimeout(Duration.ofSeconds(30)) // 读取响应超时
        .build();
    

2. 模型名称与版本

  • Ollama:模型名必须完全匹配 ollama list 的输出,包括标签(如 qwen2.5:7b、llama3.2:1b)。拉取模型时最好指定完整标签。
  • 阿里百炼:模型名是平台规定的,如 qwen-plus、qwen-max、qwen-long。在百炼的模型详情页可以找到准确的名称。用错了会报错。

3. 成本与额度管理

  • 阿里百炼的免费额度很宝贵,主要用于测试和复杂任务。千万不要在循环或高频任务中无节制地调用云端模型。可以在路由策略中加入预算控制,例如设置每日云端调用次数上限。
  • 本地模型虽然免费,但消耗计算资源。在资源有限的机器上,运行一个大参数模型可能会导致应用卡顿。根据你的硬件条件(CPU/内存/显卡)选择合适的本地模型尺寸。

4. 错误处理与降级

  • 任何网络或服务调用都可能失败。在生产环境中,必须为模型调用添加健壮的错误处理(Try-Catch)。
  • 实现降级策略:当云端模型调用失败(如超时、额度不足)时,自动降级到本地模型,哪怕回答质量差一点,也比服务完全不可用要好。
    try {
        answer = cloudModel.generate(question);
    } catch (Exception e) {
        log.warn("云端模型调用失败,降级到本地模型", e);
        answer = localModel.generate(question);
    }
    

5. 异步与非阻塞调用

  • 对于需要快速响应的应用(如Web服务),同步调用模型可能会导致请求线程被长时间挂起。LangChain4j支持异步模型接口(AsyncChatLanguageModel)。考虑使用异步客户端,或者将模型调用任务提交到单独的线程池,避免阻塞主业务线程。

6. 内容安全与审核

  • 本地模型不受内容审核。如果你构建的是对外的服务,务必对用户输入和模型输出添加必要的安全过滤和审核逻辑,防止生成有害或不适当的内容。阿里百炼的API通常自带一定程度的内容安全策略,但自定义审核仍是好习惯。

混合部署的模式为你提供了极大的灵活性,但同时也带来了架构上的复杂性。从简单的路由开始,逐步根据你的实际业务需求,增加缓存、负载均衡、监控、更智能的路由决策(甚至用一个小模型来路由)等组件,才能构建出真正稳定、高效、智能的AI应用系统。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐