Langchain4j工具调用获取不到ThreadLocal

问题描述

最近用SpringBoot开发个人项目使用了Langchain4j 1.12.2,开发一个功能,让AI输出当前用户信息,用户信息在请求拦截器中存入了ThreadLocal,工具调用时我希望拿到用户信息交给AI,但是死活拿不到,于是我给langchain4j官方github提了issue,最终找到

解决方案

① 调用ai时将数据封装到InvocationParameters中

② 工具方法上加上 InvocationParameters 参数

这样即可在运行时获取到上游传来的参数

使用案例:

java 复制代码
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.invocation.InvocationContext;
import dev.langchain4j.invocation.InvocationParameters;
import dev.langchain4j.model.openai.OpenAiStreamingChatModel;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.service.TokenStream;
import dev.langchain4j.service.UserMessage;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;

public class Langchain4jParameterTest {

    public interface MyAiService {
        TokenStream chat(@UserMessage String userMessage, InvocationParameters parameters);
    }

    @Tool( name = "getNowTime", value = "get now time")
    public String getNowTime(InvocationParameters parameters) {
        String user = parameters.get("user");
        System.out.println("\n工具调用:用户"+ user +"获取当前时间");
        return LocalDateTime.now().toString();
    }

    @Test
    public void test() {
        OpenAiStreamingChatModel streamingChatModel = OpenAiStreamingChatModel.builder()
                .modelName("模型名称")
                .apiKey("填上你的apiKey")
                .baseUrl("访问的baseUrl")
                .build();
        MyAiService aiService = AiServices.builder(MyAiService.class)
                .streamingChatModel(streamingChatModel)
                .tools(this)
                .build();
        HashMap<String, Object> map = new HashMap<>();
        map.put("user", "大大帅");
        InvocationParameters invocationParameters = InvocationParameters.from(map);
        TokenStream tokenStream = aiService.chat("现在几点了", invocationParameters);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        tokenStream.onPartialResponse(chatResponse -> {
            System.out.print(chatResponse);
        });
        tokenStream.onCompleteResponse(chatResponse -> {
            countDownLatch.countDown();
        });
        tokenStream.onError(throwable -> {
            System.out.println("【错误: " + throwable.getMessage() + "】");
            countDownLatch.countDown();
        });
        tokenStream.start();
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

输出结果:

可以看到已经获取到传递的user了

官方参考文档

https://docs.langchain4j.dev/tutorials/tools#invocationparameters

思考总结

在我debug langchain4j的源码后发现底层请求使用了JDK(JUC包下)的ForkJoinPool的线程池,导致响应后进行工具回调不在是原先的线程,因此获取不到ThreadLocal,官方提供的设置自定义线程池的api 在创建AiService时通过executeToolsConcurrently(executor)方法也无法实现ThreadLocal的正确传递,本质上只是在高层替换了线程池,在底层使用还是jdk的ForkJoinPool,简言之 是 当前线程 -> 调用自定义executor -> 调用ForkJoinPool -> 工具调用。

有些第三方AIChatModel可以传递一次InheribleThreadLocal的值,这仅仅是因为线程池新创建线程复制了一份父线程的InheribleThreadLocal值,后续线程池复用将导致无法获取到正确的InheribleThreadLocal,如:

① QwenStreamingChatModel能获取到第一次的InheribleThreadLocal

② ZhipuAiStreamingChatModel获取不到InheribleThreadLocal

③ OpenAiStreamingChatModel也获取不到InheribleThreadLocal

即使使用阿里的ttl对线程池进行包装+TransmittableThreadLocal也无法实现线程传值,我目前找到最有效的方案就是通过InvocationParameters向工具调用时传递数据,当然也可以通过InvocationContext获取(内部有一个invocationParameters属性)

相关推荐
哥布林学者10 分钟前
深度学习进阶(十七)高效通道注意力 ECA
机器学习·ai
算法与双吉汉堡16 分钟前
【Nanobot项目笔记】项目架构
python·ai·agent·智能体
菜鸟分享录18 分钟前
OpenClaw Docker一键部署(轻松实现多容器隔离)
docker·ai·openclaw·小龙虾
014-code25 分钟前
布隆过滤器:判断“可能存在“和“一定不存在“
java·redis
兔小盈26 分钟前
多线程篇-(二)线程创建、中断与终止
java·开发语言·多线程
Java编程爱好者29 分钟前
1-5 线程池:Thread+阻塞队列+循环
后端
~kiss~29 分钟前
quantizer 学习三
ai
E等于MC平方29 分钟前
AI 辅助物理课堂实验
人工智能·ai·大模型·模拟·物理·实验
jnrjian31 分钟前
Library Cache Load Lock library cache pins are replaced by mutexes
java·后端·spring
abcnull40 分钟前
传统的JavaWeb项目Demo快速学习!
java·servlet·elementui·vue·javaweb