控制Agent
通过Hooks来控制Agent的迭代,防止无限循环导致过度成本。
java
@Test
public void test19() throws GraphRunnerException {
ChatModel chatModel = CreateChatClient.createDashScopeChatModel();
ToolCallback chunkTool = FunctionToolCallback.builder("chunkTool", new ChunkTool())
.description("根据索引返回一段内容,方便agent 通过多轮 tool 调用逐步获得所有内容")
.inputType(SearchToolInput.class)
.build();
ModelLoggingHook modelLoggingHook = new ModelLoggingHook();
ReactAgent modelHook = ReactAgent.builder()
.name("model_hook")
.model(chatModel)
.tools(chunkTool)
//配置一次agent call最多只能调用7次LLM
.hooks(ModelCallLimitHook.builder().runLimit(7).build(),modelLoggingHook)
.build();
AssistantMessage message = modelHook.call("""
请严格按照顺序完成:
1.依次调用chunkTool的apply方法,从SearchToolInput的query属性的值从0到7.
2.每次只处理一段,不要跳步.
3.最后用中文总结所有内容.
""");
System.out.println(message.getText());
}
流式输出
前面有简单介绍了一下流式输出,这里再详细介绍一下。
流式输出的调用方式,通过reactAgent.stream()方法来调用实现。返回的结果是Flux类,该类是Reactor库的核心类,可以用来发射0到N个元素,非阻塞,支持AI有数据返回就能输出结果,无需等待AI全部返回数据。
这里通过subscribe方法来处理数据,它的定义如下:
java
public final Disposable subscribe(
@Nullable Consumer<? super T> consumer,
@Nullable Consumer<? super Throwable> errorConsumer,
@Nullable Runnable completeConsumer) {
return subscribe(consumer, errorConsumer, completeConsumer, (Context) null);
}
第一个参数consumer表示正常消费数据执行的方法。
第二个参数errorConsumer表示获取数据错误时执行的方法。
第三个参数completeConsumer,通过方法名也能看出这是完成时执行的方法。
所以我们的注意力主要集中在一个方法的实现上。
先看第一个例子:
这个例子测试了下面的outputType
| OutputType | 说明 |
|---|---|
AGENT_MODEL_STREAMING |
模型推理的流式增量内容 |
AGENT_MODEL_FINISHED |
模型推理完成,可获取全量内容 |
AGENT_TOOL_STREAMING |
工具调用的流式增量内容 |
AGENT_TOOL_FINISHED |
工具调用完成 |
AGENT_HOOK_STREAMING |
Hook 节点的流式增量内容 |
AGENT_HOOK_FINISHED |
Hook 节点完成 |
java
public void test20() throws GraphRunnerException, InterruptedException {
//创建tool 类
ToolCallback searchTool = FunctionToolCallback.
builder("search", new SearchTool()).description("通过给定的参数查询线上新闻并返回结果") //定义工具描述,提供给模型的使用指南
.inputType(SearchToolInput.class).build();
ChatModel chatModel = CreateChatClient.createDashScopeChatModel();
ReactAgent metaDataTool = ReactAgent.builder()
.name("meta_data_tool")
.model(chatModel)
.tools(searchTool)
.hooks(new LoggingHook())
.build();
Flux<NodeOutput> stream = metaDataTool.stream("今天发生了什么新闻");
CountDownLatch countDownLatch = new CountDownLatch(1);
stream.subscribe(
output -> {
// 检查是否为 StreamingOutput 类型
if (output instanceof StreamingOutput streamingOutput) {
OutputType type = streamingOutput.getOutputType();
// 处理模型推理的流式输出
if (type == OutputType.AGENT_MODEL_STREAMING) {
// 流式增量内容,逐步显示
System.out.print("<<<<<流式增量内容,逐步显示:>>>>" + streamingOutput.message().getText());
} else if (type == OutputType.AGENT_MODEL_FINISHED) {
// 模型推理完成,可获取完整响应
System.out.println("\n模型输出完成");
}
if (type == OutputType.AGENT_TOOL_STREAMING) {
System.out.println("<<<Tool增量内容>>> " + streamingOutput.message().getText());
}
// 处理工具调用完成(目前不支持 STREAMING)
if (type == OutputType.AGENT_TOOL_FINISHED) {
System.out.println("工具调用完成: " + output.node());
}
if (type == OutputType.AGENT_HOOK_STREAMING) {
System.out.println("<<<<Hook增量内容>>>> " + streamingOutput.message().getText());
}
// 对于 Hook 节点,通常只关注完成事件(如果Hook没有有效输出可以忽略)
if (type == OutputType.AGENT_HOOK_FINISHED) {
System.out.println("Hook 执行完成: " + output.node());
}
}
},
error -> {
System.err.println("错误: " + error);
},
() -> {
countDownLatch.countDown();
}
);
countDownLatch.await();
}
第二个例子
通过创建OpenAI的Model,这里使用的是DeepSeek的deepseek-v4-flash 模型,主要是测试Thinking的message。
java
public void test21() throws GraphRunnerException, InterruptedException {
ToolCallback searchTool = FunctionToolCallback.
builder("search", new SearchTool()).description("通过给定的参数查询线上新闻并返回结果") //定义工具描述,提供给模型的使用指南
.inputType(SearchToolInput.class).build();
ChatModel openAIModel = CreateChatClient.createOpenAIModel();
ReactAgent thinkingAgent = ReactAgent.builder()
.model(openAIModel)
.name("thinking_agent")
.tools(searchTool)
.build();
Flux<NodeOutput> stream = thinkingAgent.stream("今天发生了什么新闻");
CountDownLatch countDownLatch = new CountDownLatch(1);
stream.subscribe(output -> {
if (output instanceof StreamingOutput streamingOutput) {
OutputType type = streamingOutput.getOutputType();
Message message = streamingOutput.message();
// 处理模型流式输出
if (type == OutputType.AGENT_MODEL_STREAMING) {
if (message instanceof AssistantMessage assistantMessage) {
// 检查是否为 Thinking 消息
Object reasoningContent = assistantMessage.getMetadata().get("reasoningContent");
if (reasoningContent != null && !reasoningContent.toString().isEmpty()) {
System.out.print("[Thinking] " + reasoningContent);
} else {
// 普通模型响应(增量内容)
System.out.print(assistantMessage.getText());
}
}
}
// 处理模型输出完成
else if (type == OutputType.AGENT_MODEL_FINISHED) {
if (message instanceof AssistantMessage assistantMessage) {
if (assistantMessage.hasToolCalls()) {
// 工具调用请求
assistantMessage.getToolCalls().forEach(toolCall -> {
System.out.println("[Tool Call] " + toolCall.name() + ": " + toolCall.arguments());
});
} else {
// 模型完整响应
System.out.println("\n[Model Finished]");
}
}
}
// 处理工具执行结果
else if (type == OutputType.AGENT_TOOL_FINISHED) {
if (message instanceof ToolResponseMessage toolResponse) {
toolResponse.getResponses().forEach(response -> {
System.out.println("[Tool Result] " + response.name() + ": " + response.responseData());
});
}
}
}
},
error -> {
System.err.println("错误: " + error);
}, () -> {
countDownLatch.countDown();
});
countDownLatch.await();
}
总结
ReactAgent的定义,React(Reasoning+Acting)是结合了推理和行动的Agent的。它的功能:
1.Reasoning(思考):分析问题,决定下一步做什么。
2.Acting(行动):调用工具或生成最终的回答。
3.Observation(观察):接收工具执行的结果。
4.Iteration(迭代):基于观察结果继续思考和行动,直到完成任务。
基于上面的能力,Agent就能将复杂问题分解为多个步骤,基于中间结果动态调整策略,可以借助外部工具实现模型本身局限的功能。
Graphs的介绍: Agent 的执行路径由模型实时决策,具有动态分支和循环能力,因此需要 Graph 来描述和管理这种非固定执行流程。基于上面的知识点我们知道模型的流程是不固定的,无法使用固定流程来控制。Graph就是来解决这个问题的。注意:Agent是不能生成新的节点,这是描述Graphs容易犯的错误,它是流程是灵活的非固定的。
它是由三部分构成:
Edge(边):Node之间的流转关系,通过边来表示。有固定边和还有条件边(这是Agent的核心能力),这里是通过LLM驱动的条件边。 条件边允许根据State内容动态决定下一步执行路径,是实现Agent自主决策能力的重要机制。
Node(节点):执行单元,调用模型、Tools、Memory 查询、End节点、人工干预节点。
State(状态):共享的数据源,所有节点必须通过State交互,可以认为是OverAllState 类,通过state驱动模型走哪一条边。
Model:介绍了模型的参数配置,可以控制模型生成数据的效果。
Tools工具:定义工具的一种方式,Agent配置Tool。
Interceptor 拦截器:ModelInterceptor拦截器和 ToolInterceptor
SystemPromt 系统提示词:定义模型行为特性,有SystemPromt一般的指令和instruction更加详细的指令。
Agent的调用:基础调用call,通过invoke 调用可以获取完整状态OverAllState,它是节点间传递消息的存储介质,每个key都有自定义的更新策略。
RunnableConfig: 传递运行时的配置,可以使用threadId:区分会话。通过mate来配置系统变量和环境参数。
结构化输出: Output和OutputSchema,Output是固定的,OutputSchema更加灵活。
Memory:用来持久化上下文,默认内存。
Hook:ModelHook和AgentHook,通过Hook可以控制模型,提前结束,总结Message、日志记录等。
流式输出:可以监控模型的整个流程。