展翅的鹰 發表於 2025-9-17 22:51:00

11. LangChain4j + Tools(Function Calling)的使用详细说明

<h1 id="11-langchain4j--toolsfunction-calling的使用详细说明">11. LangChain4j + Tools(Function Calling)的使用详细说明</h1>
<p>@</p><div class="toc"><div class="toc-container-header">目录</div><ul><li>11. LangChain4j + Tools(Function Calling)的使用详细说明<ul><li>实战LangChain4j + Tools(Function Calling)<ul><li><font style="color: rgba(28, 30, 33, 1)">方式一: 低级别,使用 ChatModel 和 ToolSpecification API </font></li><li>方式二:高级别,使用 AI Services 和带有 @Tool 注解的 Java 方法</li></ul></li></ul></li><li>最后:</li></ul></div><p></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053837-541589435.png" class="lazyload"></p>
<p>官网:https://docs.langchain4j.dev/tutorials/tools</p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053782-1277643140.png" class="lazyload"></p>
<p><font style="color: rgba(28, 30, 33, 1)">有一个被称为"工具"或"函数调用"的概念。 它允许 LLM 在必要时调用一个或多个可用的工具,通常由开发者定义。 工具可以是任何东西:网络搜索、调用外部 API 或执行特定代码片段等。</font><strong><font style="color: rgba(28, 30, 33, 1)"> LLM 实际上不能自己调用工具</font></strong><font style="color: rgba(28, 30, 33, 1)">;相反,</font><strong><font style="color: rgba(28, 30, 33, 1)">它们在响应中表达调用特定工具的意图(而不是以纯文本形式响应)(比如:你要查看天气,如果它无法查找的话,它会告诉你可以去某个网站可以查找到天气:比如中国气象网站)</font></strong><font style="color: rgba(28, 30, 33, 1)">。 作为开发者,我们应该使用提供的参数执行这个工具,并将工具执行的结果反馈回来。</font></p>
<blockquote>
<p><strong>简单的一句话:就是说给我们的大模型添加了一些工具,让其可以使用这些工具,实现特定的工具,比如查看天气,查看时间等等功能</strong>。</p>
</blockquote>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053811-1096625724.png" class="lazyload"></p>
<p><strong>将LLM的智能与外部工具或API无缝连接:</strong></p>
<ul>
<li>大语言模型(LLMs)不仅仅是文本生成的能手,它们还能触发并调用第3方函数,比如查询微信/调用支付宝/查看顺丰快递单据号等等</li>
<li>重要提示:LLM本身并不执行函数,它只是指示应该调用哪个函数以及如何调用</li>
</ul>
<h2 id="实战langchain4j--toolsfunction-calling">实战LangChain4j + Tools(Function Calling)</h2>
<p>对应 Tools 的使用, LangChain4j 提供了两个抽象级别来使用工具:<br>
● 低级别,使用 ChatModel 和 ToolSpecification API<br>
● 高级别,使用 AI Services 和带有 @Tool 注解的 Java 方法</p>
<h3 id="方式一-低级别使用-chatmodel-和-toolspecification-api-"><font style="color: rgba(28, 30, 33, 1)">方式一: 低级别,使用 ChatModel 和 ToolSpecification API </font></h3>
<p><font style="color: rgba(28, 30, 33, 1)">这里我们实现让大模型获取</font><font style="color: rgba(8, 8, 8, 1); background-color: rgba(255, 255, 255, 1)">开具发票的功能,开具发票是我们自己编写的一个功能方法。</font></p>
<ol>
<li><strong>创建对应项目的 module 模块内容:</strong></li>
<li>导入相关的 pom.xml 的依赖,这里我们采用流式输出的方式,导入 整合 Spring Boot ,langchain4j-open-ai-spring-boot-starter,langchain4j-spring-boot-starter 这里我们不指定版本,而是通过继承的 pom.xml 当中获取。</li>
</ol>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053838-563924846.png" class="lazyload"></p>
<pre><code class="language-xml">       &lt;!--langchain4j-open-ai--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
            &lt;artifactId&gt;langchain4j-open-ai&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;!--langchain4j--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
            &lt;artifactId&gt;langchain4j&lt;/artifactId&gt;
      &lt;/dependency&gt;
      &lt;!--langchain4j-reactor--&gt;
      &lt;dependency&gt;
            &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
            &lt;artifactId&gt;langchain4j-reactor&lt;/artifactId&gt;
      &lt;/dependency&gt;
</code></pre>
<ol start="3">
<li><strong>设置 applcation.yaml / properties 配置文件,其中指明我们的输出响应的编码格式,因为如果不指定的话,存在返回的中文,就是乱码了。</strong></li>
</ol>
<pre><code class="language-properties">server.port=9010

spring.application.name=langchain4j-10chat-functioncalling


# 设置响应的字符编码,避免流式返回输出乱码
server.servlet.encoding.charset=utf-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true

# https://docs.langchain4j.dev/tutorials/spring-boot-integration
#langchain4j.open-ai.chat-model.api-key=${aliQwen-api}
#langchain4j.open-ai.chat-model.model-name=qwen-plus
#langchain4j.open-ai.chat-model.base-url=https://dashscope.aliyuncs.com/compatible-mode/v1


# 大模型调用不可以明文配置,你如何解决该问题
# 1 yml:                ${aliQwen-api},从环境变量读取
# 2 config配置类:      System.getenv("aliQwen-api")从环境变量读取
</code></pre>
<ol start="4">
<li><strong>新建一个,新建大模型调用的功能接口FunctionAssistant,就是我们操作大模型要做什么的接口类。这里是实现一个”开发</strong><font style="color: rgba(8, 8, 8, 1); background-color: rgba(255, 255, 255, 1)">票信息</font><strong>“功能。</strong></li>
</ol>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053810-454285848.png" class="lazyload"></p>
<pre><code class="language-java">package com.rainbowsea.langchain4jchatfunctioncalling.service;

/**
* @Description: TODO
*/
public interface FunctionAssistant
{
    //客户指令:出差住宿发票开票,
    // 开票信息:    公司名称xxx
    // 税号序列:    xx
    // 开票金额:    xxx.00元
    String chat(String message);
}

</code></pre>
<ol start="5">
<li><strong>编写大模型三件套(大模型 key,大模型 name,大模型 url) 三件套的大模型配置类。同时编写附加上,我们该实现接口类的,开具发票的功能方法。</strong></li>
</ol>
<p><strong>对标官网:</strong></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053814-1247052493.png" class="lazyload"></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053789-372119510.png" class="lazyload"></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053845-1079920237.png" class="lazyload"></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053823-712322906.png" class="lazyload"></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053880-848728401.png" class="lazyload"></p>
<pre><code class="language-java">
import com.rainbowsea.langchain4jchatfunctioncalling.service.FunctionAssistant;
import com.rainbowsea.langchain4jchatfunctioncalling.service.InvoiceHandler;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
*
*/
@Configuration
public class LLMConfig {
    @Bean
    public ChatModel chatModel() {
      return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQwen_api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    /**
   * @Description: 第一组 Low Level Tool API
   * https://docs.langchain4j.dev/tutorials/tools#low-level-tool-api
   * @Auther: zzyybs@126.com
   */
    @Bean
    public FunctionAssistant functionAssistant(ChatModel chatModel) {
      // 工具说明 ToolSpecification
      ToolSpecification toolSpecification = ToolSpecification.builder()
                .name("开具发票助手")
                .description("根据用户提交的开票信息,开具发票")
                .parameters(JsonObjectSchema.builder()
                        .addStringProperty("companyName", "公司名称")
                        .addStringProperty("dutyNumber", "税号序列")
                        .addStringProperty("amount", "开票金额,保留两位有效数字")
                        .build())
                .build();


      // 业务逻辑 ToolExecutor
      ToolExecutor toolExecutor = (toolExecutionRequest, memoryId) -&gt; {
            System.out.println(toolExecutionRequest.id());
            System.out.println(toolExecutionRequest.name());
            String arguments1 = toolExecutionRequest.arguments();
            System.out.println("arguments1****》 " + arguments1);
            return "开具成功";
      };

      return AiServices.builder(FunctionAssistant.class)
                .chatModel(chatModel)
                .tools(Map.of(toolSpecification, toolExecutor)) // Tools (Function Calling)
                .build();
    }

}
</code></pre>
<ol start="6">
<li><strong>对外编写访问的 cutroller</strong></li>
</ol>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053847-848927514.png" class="lazyload"></p>
<pre><code class="language-java">package com.rainbowsea.langchain4jchatfunctioncalling.controller;

import cn.hutool.core.date.DateUtil;
import com.rainbowsea.langchain4jchatfunctioncalling.service.FunctionAssistant;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
*/
@RestController
@Slf4j
public class ChatFunctionCallingController
{
    @Resource
    private FunctionAssistant functionAssistant;

    //http://localhost:9010/chatfunction/test1
    @GetMapping(value = "/chatfunction/test1")
    public String test1()
    {
      String chat = functionAssistant.chat("开张发票,公司:xxx科技有限公司 税号:xxx533 金额:668.12");

      System.out.println(chat);

      return "success : "+ DateUtil.now() + "\t"+chat;
    }
}

</code></pre>
<p>运行测试:</p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053786-2109666170.png" class="lazyload"></p>
<h3 id="方式二高级别使用-ai-services-和带有-tool-注解的-java-方法">方式二:高级别,使用 AI Services 和带有 @Tool 注解的 Java 方法</h3>
<p>这里我们基于方式一的基础配置,实现</p>
<p><font style="color: rgba(28, 30, 33, 1)">实现让大模型获取到天气服务,这里我们介入第三方天气服务“和风天气开发服务”</font>https://dev.qweather.com/</p>
<p><font style="color: rgba(28, 30, 33, 1)">使用注解</font></p>
<ul>
<li><strong><font style="color: rgba(28, 30, 33, 1)">@Tool,可以更方便地集成函数调用,只需将Java方法标注为</font></strong></li>
<li><strong><font style="color: rgba(28, 30, 33, 1)">@Tool,LangChain4j就会自动将其转换为ToolSpecification</font></strong></li>
</ul>
<p><strong>https://dev.qweather.com/</strong></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053815-594385685.png" class="lazyload"><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053878-1672981977.png" class="lazyload"></p>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053758-559246926.png" class="lazyload"><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053786-1850706562.png" class="lazyload"><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053823-1211332684.png" class="lazyload"><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053805-694744438.png" class="lazyload"><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053826-1881327704.png" class="lazyload"><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053875-37859147.png" class="lazyload"></p>
<ol start="2">
<li><strong>新建天气查询业务类 —— WeatherService ,就是额外附加给大模型的 Tool 工具类方法,用于获取天气的信息。</strong></li>
</ol>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053904-141527574.png" class="lazyload"></p>
<pre><code class="language-java">package com.rainbowsea.langchain4jchatfunctioncalling.service;

import com.fasterxml.jackson.databind.JsonNode;
import jakarta.annotation.Resource;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
* @auther zzyy
* @create 2025-03-12 23:24
*/
@Service
public class WeatherService
{
    //和风天气开发服务 https://dev.qweather.com/

    // 替换成你自己的和风天气API密钥
    private static final String API_KEY = System.getenv("weatherAPI");
    // 调用的url地址和指定的城市,本案例以北京为例
    private static final String BASE_URL = "https://devapi.qweather.com/v7/weather/now?location=%s&amp;key=%s";

    public JsonNode getWeatherV2(String city) throws Exception
    {
      //1 传入调用地址url和apikey
      String url = String.format(BASE_URL, city, API_KEY);

      //2 使用默认配置创建HttpClient实例
      var httpClient = HttpClients.createDefault();

      //3 创建请求工厂并将其设置给RestTemplate,开启微服务调用和风天气开发服务
      HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
      //4 RestTemplate微服务调用
      String response = new RestTemplate(factory).getForObject(url, String.class);

      //5 解析JSON响应获得第3方和风天气返回的天气预报信息
      JsonNode jsonNode = new ObjectMapper().readTree(response);

      //6 想知道具体信息和结果请查看https://dev.qweather.com/docs/api/weather/weather-now/#response
      return jsonNode;
    }
}

</code></pre>
<ol start="3">
<li><strong>新建类InvoiceHandler,添加上我们方式一开发票的功能,以及我们查询天气的功能:</strong></li>
</ol>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053929-362441087.png" class="lazyload"></p>
<pre><code class="language-java">package com.rainbowsea.langchain4jchatfunctioncalling.service;

import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import lombok.extern.slf4j.Slf4j;

/**
* 知识出处,https://docs.langchain4j.dev/tutorials/tools/#tool
*/
@Slf4j
public class InvoiceHandler
{

    @Tool("根据用户提交的开票信息进行开票")
    public String handle(@P("公司名称") String companyName,
                         @P("税号") String dutyNumber,
                         @P("金额保留两位有效数字") String amount) throws Exception
    {
      log.info("companyName =&gt;&gt;&gt;&gt; {} dutyNumber =&gt;&gt;&gt;&gt; {} amount =&gt;&gt;&gt;&gt; {}", companyName, dutyNumber, amount);
      //----------------------------------
      // 这块写自己的业务逻辑,调用redis/rabbitmq/kafka/mybatis/顺丰单据/医疗化验报告/支付接口等第3方
      //----------------------------------
      System.out.println(new WeatherService().getWeatherV2("101010100"));

      return "开票成功";
    }
}

</code></pre>
<ol start="4">
<li><strong>修改我们配置大模型的配置类,将我们编写的工具类,附加上给大模型使用,这里使用 .tools 方法</strong></li>
</ol>
<p><img alt="" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053824-91810126.png" class="lazyload"></p>
<pre><code class="language-java">package com.rainbowsea.langchain4jchatfunctioncalling.config;

import com.rainbowsea.langchain4jchatfunctioncalling.service.FunctionAssistant;
import com.rainbowsea.langchain4jchatfunctioncalling.service.InvoiceHandler;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
import dev.langchain4j.model.openai.OpenAiChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.tool.ToolExecutor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

/**
*
*/
@Configuration
public class LLMConfig {
    @Bean
    public ChatModel chatModel() {
      return OpenAiChatModel.builder()
                .apiKey(System.getenv("aliQwen_api"))
                .modelName("qwen-plus")
                .baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
                .build();
    }

    /**
   * @Description: 第二组 High Level Tool API
   * https://docs.langchain4j.dev/tutorials/tools#high-level-tool-api
   * @Auther: zzyybs@126.com
   */
    @Bean
    public FunctionAssistant functionAssistant(ChatModel chatModel)
    {
      return AiServices.builder(FunctionAssistant.class)
                  .chatModel(chatModel)
                  .tools(new InvoiceHandler())
                .build();
    }


}

</code></pre>
<h1 id="最后">最后:</h1>
<blockquote>
<p>“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”</p>
<p><img alt="在这里插入图片描述" loading="lazy" src="https://img2024.cnblogs.com/blog/3084824/202509/3084824-20250917225053922-1974273538.gif" class="lazyload"></p>
</blockquote><br><br>
来源:https://www.cnblogs.com/TheMagicalRainbowSea/p/19097820
頁: [1]
查看完整版本: 11. LangChain4j + Tools(Function Calling)的使用详细说明