J-LangChain - 复杂智能链流式执行

系列文章索引
J-LangChain 入门

介绍

j-langchain是一个Java版的LangChain开发框架,具有灵活编排和流式执行能力,旨在简化和加速各类大模型应用在Java平台的落地开发。它提供了一组实用的工具和类,使得开发人员能够更轻松地构建类似于LangChain的Java应用程序。

github: https://github.com/flower-trees/j-langchain

复杂智能链流式执行实例

1、分支路由

根据 chain 输入参数 vendor,判断使用 llama3gpt-4、还是回复 无法回答

在这里插入图片描述

LangChain实现

from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_ollama import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

prompt = ChatPromptTemplate.from_template("tell me a joke about ${topic}")
def route(info):
    if "ollama" in info["vendor"]:
        return prompt | OllamaLLM(model="llama3:8b")
    elif "chatgpt" in info["vendor"]:
        return prompt | ChatOpenAI(model="gpt-4")
    else:
        return prompt | RunnableLambda(lambda x: "sorry, I don't know how to do that")

chain = route | StrOutputParser()

result = chain.stream({"topic": "bears", "vendor": "ollama"})
for chunk in result:
    print(chunk, end="", flush=False)

J-LangChain实现

使用 flow组件 原生功能 .next() 实现分支执行 子chain

FlowInstance chain = chainActor.builder()
            ......
            .next(
                Info.c("vendor == 'ollama'", chatOllama),
                Info.c("vendor == 'chatgpt'", chatOpenAI),
                Info.c(input -> "sorry, I don't know how to do that")
            )
            ......
public void SwitchDemo() {

	BaseRunnable<StringPromptValue, ?> prompt = PromptTemplate.fromTemplate("tell me a joke about ${topic}");
    ChatOllama chatOllama = ChatOllama.builder().model("llama3:8b").build();
    ChatOpenAI chatOpenAI = ChatOpenAI.builder().model("gpt-4").build();

    FlowInstance chain = chainActor.builder()
            .next(prompt)
            .next(
                Info.c("vendor == 'ollama'", chatOllama),
                Info.c("vendor == 'chatgpt'", chatOpenAI),
                Info.c(input -> "sorry, I don't know how to do that")
            )
            .next(new StrOutputParser()).build();

    ChatGenerationChunk chunk = chainActor.stream(chain, Map.of("topic", "bears", "vendor", "ollama"));

    StringBuilder sb = new StringBuilder();
    while (chunk.getIterator().hasNext()) {
        sb.append(chunk.getIterator().next());
        System.out.println(sb);
    }
}

2、组合嵌套

主chain 调用 子chain 生成一个笑话,并对笑话是否可笑进行评价。

在这里插入图片描述

LangChain实现

from langchain_ollama import OllamaLLM
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = OllamaLLM(model="llama3:8b")
prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")

chain = prompt | model | StrOutputParser()

analysis_prompt = ChatPromptTemplate.from_template("is this a funny joke? {joke}")
composed_chain = {"joke": chain} | analysis_prompt | model | StrOutputParser()

result = composed_chain.stream({"topic": "bears"})
for chunk in result:
    print(chunk, end="", flush=False)

J-LangChain实现

flow组件 原生功能支持嵌套执行。

💡 Notes:

  • 这里需要 .next(new InvokeChain(chain)) 来一次性调用嵌套链,返回结果。
public void ComposeDemo() throws TimeoutException {

  	ChatOllama llm = ChatOllama.builder().model("llama3:8b").build();
	StrOutputParser parser = new StrOutputParser();
	
	BaseRunnable<StringPromptValue, ?> prompt = PromptTemplate.fromTemplate("tell me a joke about ${topic}");
	FlowInstance chain = chainActor.builder().next(prompt).next(llm).next(parser).build();
	
	BaseRunnable<StringPromptValue, ?> analysisPrompt = PromptTemplate.fromTemplate("is this a funny joke? ${joke}");
	
	FlowInstance analysisChain = chainActor.builder()
	        .next(new InvokeChain(chain)) //invoke 执行嵌套链
	        .next(input -> { System.out.printf("joke content: '%s' \n\n", input); return input; })
	        .next(input -> Map.of("joke", ((Generation)input).getText()))
	        .next(analysisPrompt)
	        .next(llm)
	        .next(parser).build();
	
	ChatGenerationChunk chunk = chainActor.stream(analysisChain, Map.of("topic", "bears"));
	StringBuilder sb = new StringBuilder();
	while (chunk.getIterator().hasNext()) {
	    sb.append(chunk.getIterator().next());
	    System.out.println(sb);
	}
}

3、并行执行

主chain 并行执行 joke_chainpoem_chain,并交替输出stream答案。

在这里插入图片描述

LangChain实现

from langchain_core.runnables import RunnableParallel

joke_chain = ChatPromptTemplate.from_template("tell me a joke about {topic}") | model
poem_chain = ChatPromptTemplate.from_template("write a 2-line poem about {topic}") | model

parallel_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)

result = parallel_chain.stream({"topic": "bear"})

joke = "joke: "
poem = "poem: "
for chunk in result:
    if 'joke' in chunk:  
        joke += chunk['joke']
        print(joke, flush=True)
    if 'poem' in chunk:
        poem += chunk['poem']
        print(poem, flush=True)

输出:

joke: Why
joke: Why did
joke: Why did the
poem: Bear
joke: Why did the bear
poem: Bear stands
joke: Why did the bear break
poem: Bear stands tall
joke: Why did the bear break up
poem: Bear stands tall,
joke: Why did the bear break up with
poem: Bear stands tall, wise
joke: Why did the bear break up with his
poem: Bear stands tall, wise and
......

J-LangChain实现

使用 flow组件 原生功能 .concurrent() 实现并发执行。

FlowInstance chain = chainActor.builder()
		.concurrent(
            (IResult<Map<String, AIMessageChunk>>) (iContextBus, isTimeout) ->
                    Map.of("joke", iContextBus.getResult(jokeChain.getFlowId()), "poem", iContextBus.getResult(poemChain.getFlowId())),
            jokeChain, poemChain
    	).build();

💡 Notes:

  • 这里 ChatOllama 并不是线程安全的,并发时需要 new 新实例。
public void ParallelDemo() {

    BaseRunnable<StringPromptValue, ?> joke = PromptTemplate.fromTemplate("tell me a joke about ${topic}");
    BaseRunnable<StringPromptValue, ?> poem = PromptTemplate.fromTemplate("write a 2-line poem about ${topic}");

    FlowInstance jokeChain = chainActor.builder().next(joke).next(ChatOllama.builder().model("llama3:8b").build()).build();
    FlowInstance poemChain = chainActor.builder().next(poem).next(ChatOllama.builder().model("llama3:8b").build()).build();

    FlowInstance chain = chainActor.builder().concurrent(
            (IResult<Map<String, AIMessageChunk>>) (iContextBus, isTimeout) ->
                    Map.of("joke", iContextBus.getResult(jokeChain.getFlowId()), "poem", iContextBus.getResult(poemChain.getFlowId())),
            jokeChain, poemChain
    ).build();

    Map<String, AIMessageChunk> result = chainActor.stream(chain, Map.of("topic", "bears"));

    CompletableFuture.runAsync(() -> {
        AIMessageChunk jokeChunk = result.get("joke");
        StringBuilder jokeSb = new StringBuilder().append("joke: ");
        while (true) {
            try {
                if (!jokeChunk.getIterator().hasNext()) break;
            } catch (TimeoutException e) {
                throw new RuntimeException(e);
            }
            jokeSb.append(jokeChunk.getIterator().next().getContent());
            System.out.println(jokeSb);
        }
    });

    CompletableFuture.runAsync(() -> {
        AIMessageChunk poemChunk = result.get("poem");
        StringBuilder poemSb = new StringBuilder().append("poem: ");
        while (true) {
            try {
                if (!poemChunk.getIterator().hasNext()) break;
            } catch (TimeoutException e) {
                throw new RuntimeException(e);
            }
            poemSb.append(poemChunk.getIterator().next().getContent());
            System.out.println(poemSb);
        }
    }).join();
}

4、动态路由

chain 1 总结用户问题 topic主chain 根据 topic 动态路由执行 langchain_chainanthropic_chain、或者 general_chain

在这里插入图片描述

LangChain实现
通过 RunnableLambda 实现动态路由:

from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda

chain = (
    PromptTemplate.from_template(
        """Given the user question below, classify it as either being about `LangChain`, `Anthropic`, or `Other`.

Do not respond with more than one word.

<question>
{question}
</question>

Classification:"""
    )
    | OllamaLLM(model="llama3:8b")
    | StrOutputParser()
)

langchain_chain = PromptTemplate.from_template(
    """You are an expert in langchain. \
Always answer questions starting with "As Harrison Chase told me". \
Respond to the following question:

Question: {question}
Answer:"""
) | OllamaLLM(model="llama3:8b")
anthropic_chain = PromptTemplate.from_template(
    """You are an expert in anthropic. \
Always answer questions starting with "As Dario Amodei told me". \
Respond to the following question:

Question: {question}
Answer:"""
) | OllamaLLM(model="llama3:8b")
general_chain = PromptTemplate.from_template(
    """Respond to the following question:

Question: {question}
Answer:"""
) | OllamaLLM(model="llama3:8b")

def route(info):
    if "anthropic" in info["topic"].lower():
        return anthropic_chain
    elif "langchain" in info["topic"].lower():
        return langchain_chain
    else:
        return general_chain

full_chain = {"topic": chain, "question": lambda x: x["question"]} | RunnableLambda(route)

result = full_chain.stream({"question": "how do I use LangChain?"})
for chunk in result:
    print(chunk, end="", flush=False)

J-LangChain实现
使用 flow组件 原生的功能 .next() 实现动态路由:

FlowInstance fullChain = chainActor.builder()
		......
		.next(
		        Info.c("topic == 'anthropic'", anthropicChain),
		        Info.c("topic == 'langchain'", langchainChain),
		        Info.c(generalChain)
		)
		......
public void RouteDemo() throws TimeoutException {

    ChatOllama llm = ChatOllama.builder().model("llama3:8b").build();

    BaseRunnable<StringPromptValue, Object> prompt = PromptTemplate.fromTemplate(
            """
            Given the user question below, classify it as either being about `LangChain`, `Anthropic`, or `Other`.
    
            Do not respond with more than one word.
    
            <question>
            ${question}
            </question>
    
            Classification:
            """
    );

    FlowInstance chain = chainActor.builder().next(prompt).next(llm).next(new StrOutputParser()).build();

    FlowInstance langchainChain = chainActor.builder().next(PromptTemplate.fromTemplate(
            """
            You are an expert in langchain. \
            Always answer questions starting with "As Harrison Chase told me". \
            Respond to the following question:
            
            Question: ${question}
            Answer:
            """
    )).next(ChatOllama.builder().model("llama3:8b").build()).build();

    FlowInstance anthropicChain = chainActor.builder().next(PromptTemplate.fromTemplate(
            """
            You are an expert in anthropic. \
            Always answer questions starting with "As Dario Amodei told me". \
            Respond to the following question:
        
            Question: ${question}
            Answer:
            """
    )).next(ChatOllama.builder().model("llama3:8b").build()).build();

    FlowInstance generalChain = chainActor.builder().next(PromptTemplate.fromTemplate(
            """
            Respond to the following question:
        
            Question: ${question}
            Answer:
            """
    )).next(ChatOllama.builder().model("llama3:8b").build()).build();

    FlowInstance fullChain = chainActor.builder()
            .next(new InvokeChain(chain)) //invoke 执行嵌套链
            .next(input -> { System.out.printf("topic: '%s' \n\n", input); return input; })
            .next(input -> Map.of("prompt", input, "question", ((Map<?, ?>)ContextBus.get().getFlowParam()).get("question")))
            .next(input -> { System.out.printf("topic: '%s' \n\n", input); return input; })
            .next(
                    Info.c("topic == 'anthropic'", anthropicChain),
                    Info.c("topic == 'langchain'", langchainChain),
                    Info.c(generalChain)
            ).build();

    AIMessageChunk chunk = chainActor.stream(fullChain, Map.of("question", "how do I use Anthropic?"));
    StringBuilder sb = new StringBuilder();
    while (chunk.getIterator().hasNext()) {
        sb.append(chunk.getIterator().next().getContent());
        System.out.println(sb);
    }
}

动态构建

主chain 调用 子chain 1 获取加工后的用户问题,子chain 1 根据用户输入问题是否带 对话历史,判断是调用子chain 2 根据历史修改用户问题,还是直接透传用户问题,主chain 根据最终问题,并添加 system 内容后,给出答案。

在这里插入图片描述

LangChain实现

from langchain_core.runnables import chain, RunnablePassthrough

llm = OllamaLLM(model="llama3:8b")

contextualize_instructions = """Convert the latest user question into a standalone question given the chat history. Don't answer the question, return the question and nothing else (no descriptive text)."""
contextualize_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", contextualize_instructions),
        ("placeholder", "{chat_history}"),
        ("human", "{question}"),
    ]
)
contextualize_question = contextualize_prompt | llm | StrOutputParser()

@chain
def contextualize_if_needed(input_: dict):
    if input_.get("chat_history"):
        return contextualize_question
    else:
        return RunnablePassthrough() | itemgetter("question")

@chain
def fake_retriever(input_: dict):
    return "egypt's population in 2024 is about 111 million"

qa_instructions = (
    """Answer the user question given the following context:\n\n{context}."""
)
qa_prompt = ChatPromptTemplate.from_messages(
    [("system", qa_instructions), ("human", "{question}")]
)

full_chain = (
    RunnablePassthrough.assign(question=contextualize_if_needed).assign(
        context=fake_retriever
    )
    | qa_prompt
    | llm
    | StrOutputParser()
)

result = full_chain.stream({
    "question": "what about egypt",
    "chat_history": [
        ("human", "what's the population of indonesia"),
        ("ai", "about 276 million"),
    ],
})
for chunk in result:
    print(chunk, end="", flush=False)

J-LangChain实现
使用 flow组件 原生功能 .all() 实现执行 子chain 获取问题,并结合 system内容 给出答案的功能。

FlowInstance fullChain = chainActor.builder()
           .all(
                   (iContextBus, isTimeout) -> Map.of(
                           "question", iContextBus.getResult(contextualizeIfNeeded.getFlowId()).toString(),
                           "context", iContextBus.getResult("fakeRetriever")),
                   Info.c(contextualizeIfNeeded),
                   Info.c(input -> "egypt's population in 2024 is about 111 million").cAlias("fakeRetriever")
           )
           ......
public void DynamicDemo() throws TimeoutException {

    ChatOllama llm = ChatOllama.builder().model("llama3:8b").build();

    String contextualizeInstructions = """
            Convert the latest user question into a standalone question given the chat history. Don't answer the question, return the question and nothing else (no descriptive text).""";

    BaseRunnable<ChatPromptValue, Object> contextualizePrompt = ChatPromptTemplate.fromMessages(
            List.of(
                    Pair.of("system", contextualizeInstructions),
                    Pair.of("placeholder", "${chatHistory}"),
                    Pair.of("human", "${question}")
            )
    );

    FlowInstance contextualizeQuestion = chainActor.builder()
            .next(contextualizePrompt)
            .next(llm)
            .next(new StrOutputParser())
            .build();

    FlowInstance contextualizeIfNeeded = chainActor.builder().next(
            Info.c("chatHistory != null", new InvokeChain(contextualizeQuestion)),
            Info.c(input -> Map.of("question", ((Map<String, String>)input).get("question")))
    ).build();

    String qaInstructions =
            """
            Answer the user question given the following context:\n\n${context}.
            """;
    BaseRunnable<ChatPromptValue, Object>  qaPrompt = ChatPromptTemplate.fromMessages(
            List.of(
                    Pair.of("system", qaInstructions),
                    Pair.of("human", "${question}")
            )
    );

    FlowInstance fullChain = chainActor.builder()
            .all(
                    (iContextBus, isTimeout) -> Map.of(
                            "question", iContextBus.getResult(contextualizeIfNeeded.getFlowId()).toString(),
                            "context", iContextBus.getResult("fakeRetriever")),
                    Info.c(contextualizeIfNeeded),
                    Info.c(input -> "egypt's population in 2024 is about 111 million").cAlias("fakeRetriever")
            )
            .next(qaPrompt)
            .next(input -> { System.out.printf("topic: '%s' \n\n", JsonUtil.toJson(input)); return input; })
            .next(llm)
            .next(new StrOutputParser())
            .build();

    ChatGenerationChunk chunk = chainActor.stream(fullChain,
            Map.of(
                    "question", "what about egypt",
                    "chatHistory",
                            List.of(
                                    Pair.of("human", "what's the population of indonesia"),
                                    Pair.of("ai", "about 276 million")
                            )
            )
    );
    StringBuilder sb = new StringBuilder();
    while (chunk.getIterator().hasNext()) {
        sb.append(chunk.getIterator().next().getText());
        System.out.println(sb);
    }
}
<think>好的,我现在需要解决用户在使用langchain4j工具时遇到的流式响应执行错误。首先,我得了解langchain4j的基本情况,它是一个JavaLangChain实现库,用于构建基于大语言模型的应用程序。用户提到的问题是关于工具(tools)中的流式响应执行错误,因此需要先明确错误的具体表现和上下文。 首先,我应该回忆一下类似的问题处理经验。比如,在LangGraph中,异步和流处理是关键特性,可能涉及到任务调度、数据流管理等问题[^1]。用户提到的流式响应可能涉及数据流的异步处理,而执行错误可能与线程管理、资源释放或数据流中断有关。 接下来,我需要考虑常见的流式处理错误原因。根据Flink处理流数据的经验,性能指标如延迟、吞吐量、记录处理和GC暂停都可能影响流式任务的稳定性[^2]。虽然Flink和langchain4j是不同的框架,但流处理的共性问题可能存在相似之处,比如资源竞争、背压(backpressure)处理不当,或者异步回调未正确实现。 然后,我需要针对langchain4j的具体情况进行分析。可能的错误原因包括: 1. **流式响应配置错误**:检查是否正确配置了流式处理相关的参数,比如超时设置、缓冲区大小或异步回调函数。 2. **线程管理问题**:流式处理通常涉及多线程或异步任务,如果线程池配置不当,可能导致资源耗尽或任务阻塞。 3. **依赖版本不兼容**:检查langchain4j及其相关依赖(如HTTP客户端、异步库)的版本是否兼容,是否存在已知的bug。 4. **网络或IO异常**:流式响应可能需要维持长连接,网络波动或服务器中断可能导致错误。 5. **数据处理逻辑错误**:在流式处理过程中,数据处理逻辑可能有错误,比如未正确处理分块数据或未及时关闭流。 接下来,我应该思考如何验证这些可能性。例如,用户可以通过日志或堆栈跟踪定位错误发生的具体位置。如果是配置问题,查看相关配置项是否正确;如果是线程问题,检查线程池的使用情况和资源监控;如果是依赖问题,查看官方文档或社区是否有关类似问题的报告。 此外,参考LangGraph的异步和流处理机制,可能需要确保流式响应的生命周期管理正确,比如及时释放资源或处理取消信号[^1]。在代码层面,可能需要检查是否正确实现了流式回调接口,是否有未捕获的异常导致流程中断。 最后,总结解决方案步骤:确认错误日志、检查配置和依赖、验证线程和资源管理、确保正确处理流数据块、测试网络稳定性。如果问题依旧,可能需要查阅langchain4j的官方文档或社区支持,寻求更具体的帮助。</think>针对langchain4j工具中流式响应执行错误的解决方案,请按照以下步骤排查: ### 一、基础配置检查 1. **版本兼容性验证** - 确认使用的langchain4j版本与Java版本匹配(最低要求Java 11) - 检查依赖管理工具中是否存在冲突库: ```xml <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-openai</artifactId> <version>0.27.0</version> <!-- 最新稳定版 --> </dependency> ``` 2. **流式配置参数** - 检查OpenAI客户端配置是否启用流式模式: ```java OpenAiStreamingChatModel model = OpenAiStreamingChatModel.builder() .apiKey("sk-...") .modelName("gpt-4") .temperature(0.7) .logStreaming(true) // 启用流式日志 .build(); ``` ### 二、异步处理验证 1. **线程池配置**(参考LangGraph异步处理机制) ```java ExecutorService executor = Executors.newFixedThreadPool(10); // 根据业务需求调整 StreamingResponseHandler handler = new StreamingResponseHandler() { @Override public void onNext(String token) { // 保证线程安全操作 } @Override public void onError(Throwable error) { error.printStackTrace(); // 必须实现错误处理 } }; ``` 2. **响应生命周期管理** ```java try (StreamingChatModel model = createModel()) { model.generate(userMessage, handler); // 自动资源释放 } catch (IOException e) { // 处理资源泄漏异常 } ``` ### 三、常见错误模式及修复 | 错误现象 | 可能原因 | 解决方案 | |---------|---------|---------| | 响应中断 | 网络超时 | 设置合理超时:<br>`model.timeout(Duration.ofSeconds(30))` | | 部分内容丢失 | 缓冲区溢出 | 调整缓冲区大小:<br>`model.maxRetries(3).bufferSize(8192)` | | 线程阻塞 | 资源竞争 | 使用异步非阻塞IO:<br>`AsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder().setIoThreadsCount(4).build()` | ### 四、调试建议 1. 启用详细日志: ```java OpenAiStreamingChatModel model = OpenAiStreamingChatModel.builder() //... .logRequests(true) .logResponses(true) .build(); ``` 2. 使用单元测试验证流式处理: ```java @Test void testStreaming() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); model.generate("Hello", new StreamingResponseHandler() { //... @Override public void onComplete() { latch.countDown(); } }); assertTrue(latch.await(30, SECONDS)); } ``` ### 五、性能优化(参考Flink监控指标) 1. 监控关键指标: ```java MeterRegistry registry = new SimpleMeterRegistry(); model.meterRegistry(registry); // 绑定指标监控 ``` 2. 分析处理延迟: ```java Timer timer = registry.timer("streaming.latency"); timer.record(() -> { model.generate(message, handler); }); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值