mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-10 11:17:00 +08:00
feat(react-agent): log ReAct reason/act events with sessionId and config toggle
Subscribe to agentscope Pre/PostReasoningEvent, Pre/PostActingEvent and ErrorEvent through a new ReActLoggingHook, surfacing the agent's internal think-act loop in standard logs. Each line carries the LiteFlow agent sessionId so concurrent sessions stay distinguishable. ReActAgentComponent attaches the hook automatically alongside any user-provided hooks. Toggle via liteflow.agent.logging.react-enabled (default true) or override enableReActLogging() per component. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ public class AgentConfig {
|
||||
private SessionConfig session = new SessionConfig();
|
||||
private ShellConfig shell = new ShellConfig();
|
||||
private DefaultsConfig defaults = new DefaultsConfig();
|
||||
private LoggingConfig logging = new LoggingConfig();
|
||||
private PlatformCredential openai = new PlatformCredential();
|
||||
private PlatformCredential anthropic = new PlatformCredential();
|
||||
private PlatformCredential gemini = new PlatformCredential();
|
||||
@@ -23,6 +24,8 @@ public class AgentConfig {
|
||||
public void setShell(ShellConfig v) { this.shell = v; }
|
||||
public DefaultsConfig getDefaults() { return defaults; }
|
||||
public void setDefaults(DefaultsConfig v) { this.defaults = v; }
|
||||
public LoggingConfig getLogging() { return logging; }
|
||||
public void setLogging(LoggingConfig v) { this.logging = v; }
|
||||
public PlatformCredential getOpenai() { return openai; }
|
||||
public void setOpenai(PlatformCredential v) { this.openai = v; }
|
||||
public PlatformCredential getAnthropic() { return anthropic; }
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.yomahub.liteflow.property.agent;
|
||||
|
||||
/**
|
||||
* ReAct agent 日志开关配置。
|
||||
* <p>对应 {@code liteflow.agent.logging.*} 配置段。
|
||||
*/
|
||||
public class LoggingConfig {
|
||||
|
||||
/** 是否输出 reason / act / error 内部事件日志。默认开启。 */
|
||||
private boolean reactEnabled = true;
|
||||
|
||||
public boolean isReactEnabled() { return reactEnabled; }
|
||||
public void setReactEnabled(boolean reactEnabled) { this.reactEnabled = reactEnabled; }
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.yomahub.liteflow.agent.component;
|
||||
|
||||
import com.yomahub.liteflow.agent.exception.AgentConfigException;
|
||||
import com.yomahub.liteflow.agent.hook.ReActLoggingHook;
|
||||
import com.yomahub.liteflow.agent.session.AgentSession;
|
||||
import com.yomahub.liteflow.agent.session.AgentSessionManager;
|
||||
import com.yomahub.liteflow.agent.session.NanoIdSessionIdGenerator;
|
||||
import com.yomahub.liteflow.agent.tool.ManagedShellCommandTool;
|
||||
import com.yomahub.liteflow.agent.tool.WorkspaceFileTools;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
@@ -21,6 +23,7 @@ import io.agentscope.core.tool.Toolkit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -92,9 +95,11 @@ public abstract class ReActAgentComponent extends NodeComponent {
|
||||
protected List<Object> tools(ReActAgentContext ctx) { return List.of(); }
|
||||
|
||||
/**
|
||||
* 从当前 slot 推导 session id。默认使用 slot 的 requestId。
|
||||
* 从当前 slot 推导 session id。默认生成 {@code YYYYMMDD + NanoId(18)} 格式。
|
||||
*/
|
||||
protected String resolveSessionId(Slot slot) { return slot.getRequestId(); }
|
||||
protected String resolveSessionId(Slot slot) {
|
||||
return NanoIdSessionIdGenerator.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* ReAct 最大迭代次数。返回 -1(默认值)表示使用
|
||||
@@ -117,6 +122,16 @@ public abstract class ReActAgentComponent extends NodeComponent {
|
||||
*/
|
||||
protected List<Hook> hooks(ReActAgentContext ctx) { return List.of(); }
|
||||
|
||||
/**
|
||||
* 是否在日志中输出 agent 的 reason / act / error 事件。
|
||||
* <p>默认从配置 {@code liteflow.agent.logging.react-enabled} 读取(默认 true),
|
||||
* 子类可覆写返回 {@code true}/{@code false} 强制开关。
|
||||
* 输出在 logger {@code com.yomahub.liteflow.agent.hook.ReActLoggingHook} 上。
|
||||
*/
|
||||
protected boolean enableReActLogging() {
|
||||
return agentConfig().getLogging().isReactEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* agent 回复后调用。默认实现会把 {@code reply.getTextContent()}
|
||||
* 写入 slot 的响应数据。
|
||||
@@ -194,6 +209,11 @@ public abstract class ReActAgentComponent extends NodeComponent {
|
||||
toolkit.registerTool(new ManagedShellCommandTool(ctx.getWorkspaceDir(), cfg));
|
||||
}
|
||||
|
||||
List<Hook> allHooks = new ArrayList<>(hooks(ctx));
|
||||
if (enableReActLogging()) {
|
||||
allHooks.add(new ReActLoggingHook(ctx.getSessionId()));
|
||||
}
|
||||
|
||||
return ReActAgent.builder()
|
||||
.name(getNodeId() == null ? "liteflow-agent" : getNodeId())
|
||||
.sysPrompt(systemPrompt(ctx))
|
||||
@@ -201,7 +221,7 @@ public abstract class ReActAgentComponent extends NodeComponent {
|
||||
.toolkit(toolkit)
|
||||
.memory(new InMemoryMemory())
|
||||
.maxIters(iters)
|
||||
.hooks(hooks(ctx))
|
||||
.hooks(allHooks)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.yomahub.liteflow.agent.hook;
|
||||
|
||||
import io.agentscope.core.hook.ErrorEvent;
|
||||
import io.agentscope.core.hook.Hook;
|
||||
import io.agentscope.core.hook.HookEvent;
|
||||
import io.agentscope.core.hook.PostActingEvent;
|
||||
import io.agentscope.core.hook.PostReasoningEvent;
|
||||
import io.agentscope.core.hook.PreActingEvent;
|
||||
import io.agentscope.core.hook.PreReasoningEvent;
|
||||
import io.agentscope.core.message.Msg;
|
||||
import io.agentscope.core.message.ToolResultBlock;
|
||||
import io.agentscope.core.message.ToolUseBlock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 把 agentscope ReActAgent 的内部 Reason / Act / Error 事件输出到日志,
|
||||
* 让 LiteFlow 用户在终端可以直接看到 agent 的思考与工具调用过程。
|
||||
*
|
||||
* <p>事件 → 日志格式:
|
||||
* <ul>
|
||||
* <li>{@link PreReasoningEvent}:{@code [agent:reason] >>> model=... messages=N}</li>
|
||||
* <li>{@link PostReasoningEvent}:{@code [agent:reason] <<< text=... toolCalls=[...]}</li>
|
||||
* <li>{@link PreActingEvent}:{@code [agent:act] >>> tool=... input=...}</li>
|
||||
* <li>{@link PostActingEvent}:{@code [agent:act] <<< tool=... result=...}</li>
|
||||
* <li>{@link ErrorEvent}:{@code [agent:error] ...}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class ReActLoggingHook implements Hook {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ReActLoggingHook.class);
|
||||
private static final int MAX_TEXT_LEN = 500;
|
||||
|
||||
private final String sessionId;
|
||||
|
||||
public ReActLoggingHook(String sessionId) {
|
||||
this.sessionId = sessionId == null ? "-" : sessionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends HookEvent> Mono<T> onEvent(T event) {
|
||||
try {
|
||||
if (event instanceof PreReasoningEvent e) {
|
||||
List<Msg> msgs = e.getInputMessages();
|
||||
LOG.info("[agent:reason][{}] >>> model={} messages={}",
|
||||
sessionId, e.getModelName(), msgs == null ? 0 : msgs.size());
|
||||
} else if (event instanceof PostReasoningEvent e) {
|
||||
Msg reply = e.getReasoningMessage();
|
||||
String text = reply == null ? "" : truncate(reply.getTextContent());
|
||||
List<ToolUseBlock> tools = reply == null
|
||||
? List.of()
|
||||
: reply.getContentBlocks(ToolUseBlock.class);
|
||||
if (tools.isEmpty()) {
|
||||
LOG.info("[agent:reason][{}] <<< text={}", sessionId, text);
|
||||
} else {
|
||||
LOG.info("[agent:reason][{}] <<< text={} toolCalls={}",
|
||||
sessionId, text, summarizeToolUses(tools));
|
||||
}
|
||||
} else if (event instanceof PreActingEvent e) {
|
||||
ToolUseBlock t = e.getToolUse();
|
||||
LOG.info("[agent:act][{}] >>> tool={} input={}",
|
||||
sessionId, t.getName(), truncate(String.valueOf(t.getInput())));
|
||||
} else if (event instanceof PostActingEvent e) {
|
||||
ToolResultBlock r = e.getToolResult();
|
||||
LOG.info("[agent:act][{}] <<< tool={} result={}",
|
||||
sessionId, r.getName(), truncate(blocksToString(r)));
|
||||
} else if (event instanceof ErrorEvent e) {
|
||||
LOG.warn("[agent:error][{}] {}", sessionId, e.getError().toString(), e.getError());
|
||||
}
|
||||
} catch (Throwable logEx) {
|
||||
LOG.debug("ReActLoggingHook formatting failed", logEx);
|
||||
}
|
||||
return Mono.just(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return 900;
|
||||
}
|
||||
|
||||
private static String summarizeToolUses(List<ToolUseBlock> tools) {
|
||||
StringBuilder sb = new StringBuilder("[");
|
||||
for (int i = 0; i < tools.size(); i++) {
|
||||
ToolUseBlock t = tools.get(i);
|
||||
if (i > 0) sb.append(", ");
|
||||
sb.append(t.getName()).append("(").append(t.getInput()).append(")");
|
||||
}
|
||||
return truncate(sb.append("]").toString());
|
||||
}
|
||||
|
||||
private static String blocksToString(ToolResultBlock r) {
|
||||
if (r.getOutput() == null) return "";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
r.getOutput().forEach(b -> sb.append(b));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String truncate(String s) {
|
||||
if (s == null) return "";
|
||||
s = s.replaceAll("\\s+", " ").trim();
|
||||
return s.length() <= MAX_TEXT_LEN ? s : s.substring(0, MAX_TEXT_LEN) + "...(truncated)";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user