以下是一个完整的示例,展示如何在扩展节点、人类反馈节点和翻译节点之间实现中断和恢复:
节点定义
ExpanderNode
package com.spring.ai.tutorial.graph.human.node;
import com.alibaba.cloud.ai.graph.NodeOutput;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.alibaba.cloud.ai.graph.async.AsyncGenerator;
import com.alibaba.cloud.ai.graph.streaming.StreamingChatGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.PromptTemplate;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class ExpanderNode implements NodeAction {
private static final Logger logger = LoggerFactory.getLogger(ExpanderNode.class);
private static final PromptTemplate DEFAULTPROMPTTEMPLATE = new PromptTemplate(
"You are an expert at information retrieval and search optimization.
" +
"Your task is to generate {number} different versions of the given query.
" +
"Each variant must cover different perspectives or aspects of the topic,
" +
"while maintaining the core intent of the original query. The goal is to
" +
"expand the search space and improve the chances of finding relevant information.
" +
"Do not explain your choices or add any other text.
" +
"Provide the query variants separated by newlines.
" +
"Original query: {query}
" +
"Query variants:
"
);
private final ChatClient chatClient;
private final Integer NUMBER = 3;
public ExpanderNode(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) {
logger.info("expander node is running.");
String query = state.value("query", "");
Integer expanderNumber = state.value("expandernumber", this.NUMBER);
Flux<ChatResponse> chatResponseFlux = this.chatClient.prompt()
.user((user) -> user.text(DEFAULTPROMPTTEMPLATE.getTemplate())
.param("number", expanderNumber)
.param("query", query))
.stream()
.chatResponse();
AsyncGenerator<? extends NodeOutput> generator = StreamingChatGenerator.builder()
.startingNode("expanderllmstream")
.startingState(state)
.mapResult(response -> {
String text = response.getResult().getOutput().getText();
List<String> queryVariants = Arrays.asList(text.split("
"));
return Map.of("expandercontent", queryVariants);
})
.build(chatResponseFlux);
return Map.of("expandercontent", generator);
}
}
TranslateNode
package com.spring.ai.tutorial.graph.human.node;
import com.alibaba.cloud.ai.graph.NodeOutput;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.alibaba.cloud.ai.graph.async.AsyncGenerator;
import com.alibaba.cloud.ai.graph.streaming.StreamingChatGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.PromptTemplate;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class TranslateNode implements NodeAction {
private static final Logger logger = LoggerFactory.getLogger(TranslateNode.class);
private static final PromptTemplate DEFAULTPROMPTTEMPLATE = new PromptTemplate(
"Given a user query, translate it to {targetLanguage}.
" +
"If the query is already in {targetLanguage}, return it unchanged.
" +
"If you don't know the language of the query, return it unchanged.
" +
"Do not add explanations nor any other text.
" +
"Original query: {query}
" +
"Translated query:
"
);
private final ChatClient chatClient;
private final String TARGETLANGUAGE = "English";
public TranslateNode(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) {
logger.info("translate node is running.");
String query = state.value("query", "");
String targetLanguage = state.value("translatelanguage", TARGETLANGUAGE);
Flux<ChatResponse> chatResponseFlux = this.chatClient.prompt()
.user((user) -> user.text(DEFAULTPROMPTTEMPLATE.getTemplate())
.param("targetLanguage", targetLanguage)
.param("query", query))
.stream()
.chatResponse();
AsyncGenerator<? extends NodeOutput> generator = StreamingChatGenerator.builder()
.startingNode("translatellmstream")
.startingState(state)
.mapResult(response -> {
String text = response.getResult().getOutput().getText();
List<String> queryVariants = Arrays.asList(text.split("
"));
return Map.of("translatecontent", queryVariants);
})
.build(chatResponseFlux);
return Map.of("translatecontent", generator);
}
}
边定义
package com.spring.ai.tutorial.graph.human.dispatcher;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.action.EdgeAction;
public class HumanFeedbackDispatcher implements EdgeAction {
@Override
public String apply(OverAllState state) throws Exception {
return (String) state.value("humannextnode", StateGraph.END);
}
}
- Checkpoint 机制 – 状态持久化
- 快速入门 – Graph 基础使用
- 流式处理 – 流式输出
本文整理自 Spring AI Alibaba 官方文档《人类反馈》:https://java2ai.com/docs/frameworks/graph-core/examples/human-in-the-loop

AI项目博客






