perf: 优化中断续聊表现,被中断的回复仍可以进入上下文中,保证记忆连续性
- 续聊逻辑优化
This commit is contained in:
@@ -19,6 +19,7 @@ import com.easyagents.agent.runtime.skill.AgentSkillBinding;
|
||||
import com.easyagents.agent.runtime.skill.AgentSkillRuntimeContext;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolInvoker;
|
||||
import com.easyagents.agent.runtime.tool.AgentToolSpec;
|
||||
import com.easyagents.agent.runtime.tool.operate.AgentOperateToolAdapter;
|
||||
import io.agentscope.core.ReActAgent;
|
||||
import io.agentscope.core.agent.Event;
|
||||
import io.agentscope.core.agent.EventType;
|
||||
@@ -55,6 +56,7 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
private final AgentScopeMemoryAdapter memoryAdapter;
|
||||
private final AgentScopeSkillAdapter skillAdapter;
|
||||
private final AgentScopeMessageAdapter messageAdapter;
|
||||
private final AgentOperateToolAdapter operateToolAdapter = new AgentOperateToolAdapter();
|
||||
private final AgentKnowledgeCitationMatcher citationMatcher = new HeuristicKnowledgeCitationMatcher();
|
||||
private final AtomicBoolean initialized = new AtomicBoolean(false);
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
@@ -153,7 +155,9 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
}
|
||||
AgentRuntimeExecutionContext executionContext = createResumeExecutionContext(request);
|
||||
try {
|
||||
approvalCoordinator.consume(request);
|
||||
if (!request.isTrusted()) {
|
||||
approvalCoordinator.consume(request);
|
||||
}
|
||||
} catch (RuntimeException error) {
|
||||
running.set(false);
|
||||
throw error;
|
||||
@@ -246,7 +250,7 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
// 输出所有事件统一交给调用方存储事件记录,默认是空实现即不记录。
|
||||
.doOnNext(event -> executionContext.getConversationRecorder().record(executionContext, event))
|
||||
// 处理中断请求
|
||||
.doOnCancel(() -> cancelInternal(executionContext, sideEvents, cancelled))
|
||||
.doOnCancel(() -> cancelInternal(executionContext, sideEvents, finalText, finalMessage, cancelled))
|
||||
// 释放运行锁并清掉 turn context。
|
||||
.doFinally(signalType -> cleanupTurn());
|
||||
}
|
||||
@@ -608,18 +612,90 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
*
|
||||
* @param context 本轮运行上下文
|
||||
* @param sideEvents 旁路事件 sink
|
||||
* @param finalText 当前已累计的助手文本
|
||||
* @param finalMessage 当前已捕获的结构化助手消息
|
||||
* @param cancelled 取消去重标记
|
||||
*/
|
||||
private void cancelInternal(AgentRuntimeExecutionContext context,
|
||||
Sinks.Many<AgentRuntimeEvent> sideEvents,
|
||||
StringBuilder finalText,
|
||||
AtomicReference<AgentMessage> finalMessage,
|
||||
AtomicBoolean cancelled) {
|
||||
if (!cancelled.compareAndSet(false, true)) {
|
||||
return;
|
||||
}
|
||||
context.setCancelReason("cancelled");
|
||||
approvalCoordinator.cancelAll(context.getCancelReason());
|
||||
agent.interrupt();
|
||||
sideEvents.tryEmitComplete();
|
||||
try {
|
||||
approvalCoordinator.cancelAll(context.getCancelReason());
|
||||
agent.interrupt();
|
||||
persistPartialAssistantOnCancel(finalText, finalMessage);
|
||||
} finally {
|
||||
sideEvents.tryEmitComplete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将取消前已输出的助手内容补写入 AgentScope memory 并保存 session。
|
||||
*
|
||||
* <p>AgentScope 的正常完成路径会自行把最终助手消息写入 memory。取消订阅时不会触发
|
||||
* 完成路径,因此这里仅在已有非空助手内容时补写一次,确保下一轮对话能拿到中断前上下文。</p>
|
||||
*
|
||||
* @param finalText 当前已累计的助手文本
|
||||
* @param finalMessage 当前已捕获的结构化助手消息
|
||||
*/
|
||||
private void persistPartialAssistantOnCancel(StringBuilder finalText,
|
||||
AtomicReference<AgentMessage> finalMessage) {
|
||||
AgentMessage partialMessage = partialAssistantMessage(finalText, finalMessage);
|
||||
if (partialMessage == null) {
|
||||
return;
|
||||
}
|
||||
agent.getMemory().addMessage(messageAdapter.toMsg(partialMessage));
|
||||
saveSession();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成取消时可写入 memory 的助手消息。
|
||||
*
|
||||
* @param finalText 当前已累计的助手文本
|
||||
* @param finalMessage 当前已捕获的结构化助手消息
|
||||
* @return 非空助手消息,不存在有效内容时返回 null
|
||||
*/
|
||||
private AgentMessage partialAssistantMessage(StringBuilder finalText,
|
||||
AtomicReference<AgentMessage> finalMessage) {
|
||||
AgentMessage message = finalMessage == null ? null : finalMessage.get();
|
||||
if (hasContent(message)) {
|
||||
message.setRole(AgentMessageRole.ASSISTANT);
|
||||
return message;
|
||||
}
|
||||
String text = finalText == null ? "" : finalText.toString();
|
||||
if (text.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
return AgentMessage.text(AgentMessageRole.ASSISTANT, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断消息是否有可用于上下文的内容块。
|
||||
*
|
||||
* @param message 消息
|
||||
* @return 存在非空内容块时为 true
|
||||
*/
|
||||
private boolean hasContent(AgentMessage message) {
|
||||
if (message == null || message.getContentBlocks() == null || message.getContentBlocks().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (AgentContentBlock block : message.getContentBlocks()) {
|
||||
if (block instanceof AgentTextBlock textBlock) {
|
||||
if (textBlock.getText() != null && !textBlock.getText().isBlank()) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (block != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -920,6 +996,19 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
if (definition.getModelSpec() == null) {
|
||||
throw new AgentRuntimeException("Agent model spec is required.");
|
||||
}
|
||||
validateOperateToolConflicts(definition);
|
||||
}
|
||||
|
||||
private void validateOperateToolConflicts(AgentDefinition definition) {
|
||||
Set<String> operateToolNames = operateToolAdapter.enabledToolNames(definition.getOperateToolSpecs());
|
||||
if (operateToolNames.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (AgentToolSpec toolSpec : definition.getToolSpecs()) {
|
||||
if (toolSpec != null && operateToolNames.contains(toolSpec.getName())) {
|
||||
throw new AgentRuntimeException("Agent operate tool conflicts with existing tool: " + toolSpec.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -952,7 +1041,8 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
AgentDefinition definition = context.getAgentDefinition();
|
||||
Model model = modelFactory.create(definition.getModelSpec(), definition.getGenerationOptions());
|
||||
Toolkit toolkit = new Toolkit();
|
||||
Map<String, List<AgentTool>> skillTools = buildToolkit(context, toolkit);
|
||||
AgentScopeToolkitBuildResult toolkitBuildResult = buildToolkit(context, toolkit);
|
||||
Map<String, List<AgentTool>> skillTools = toolkitBuildResult.skillTools();
|
||||
AgentScopeMemoryBuildResult memoryResult = memoryAdapter.createMemoryResult(null, definition.getMemoryPolicy(), model);
|
||||
Memory memory = memoryResult.getMemory();
|
||||
Knowledge knowledge = knowledgeAdapter.createAggregateKnowledge(context, turnContextHolder);
|
||||
@@ -964,7 +1054,8 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
if (memory instanceof AutoContextMemory) {
|
||||
interceptors.add(new AutoContextInterceptor(eventBridge, memoryResult.getAutoContextConfig()));
|
||||
}
|
||||
interceptors.add(new ToolHitlInterceptor(eventBridge, approvalCoordinator, definition.getToolSpecs()));
|
||||
interceptors.add(new ToolHitlInterceptor(eventBridge, approvalCoordinator,
|
||||
mergeToolSpecs(definition.getToolSpecs(), toolkitBuildResult.operateToolSpecs())));
|
||||
// 注册旁路事件监听器与主线路干预器。观察器只发旁路事件,不修改 AgentScope HookEvent。
|
||||
List<AgentRuntimeObserver> observers = new ArrayList<>();
|
||||
observers.add(new SkillExecutionObserver(eventBridge, skillContext, skillBox));
|
||||
@@ -1003,11 +1094,11 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
* @param toolkit AgentScope Toolkit
|
||||
* @return 按 Skill ID 分组的工具
|
||||
*/
|
||||
private Map<String, List<AgentTool>> buildToolkit(AgentRuntimeExecutionContext context,
|
||||
private AgentScopeToolkitBuildResult buildToolkit(AgentRuntimeExecutionContext context,
|
||||
Toolkit toolkit) {
|
||||
Map<String, List<AgentTool>> skillTools = new LinkedHashMap<>();
|
||||
if (!context.getAgentDefinition().getExecutionOptions().isToolCallingEnabled()) {
|
||||
return skillTools;
|
||||
return new AgentScopeToolkitBuildResult(skillTools, List.of());
|
||||
}
|
||||
for (AgentToolSpec toolSpec : context.getAgentDefinition().getToolSpecs()) {
|
||||
AgentToolInvoker invoker = context.getToolInvokers().get(toolSpec.getName());
|
||||
@@ -1020,7 +1111,20 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
skillTools.computeIfAbsent(skillBinding.getSkillId(), key -> new ArrayList<>()).add(agentTool);
|
||||
}
|
||||
}
|
||||
return skillTools;
|
||||
List<AgentToolSpec> operateToolSpecs = operateToolAdapter.register(
|
||||
context.getAgentDefinition().getOperateToolSpecs(), toolkit);
|
||||
return new AgentScopeToolkitBuildResult(skillTools, operateToolSpecs);
|
||||
}
|
||||
|
||||
private List<AgentToolSpec> mergeToolSpecs(List<AgentToolSpec> toolSpecs, List<AgentToolSpec> operateToolSpecs) {
|
||||
List<AgentToolSpec> merged = new ArrayList<>();
|
||||
if (toolSpecs != null) {
|
||||
merged.addAll(toolSpecs);
|
||||
}
|
||||
if (operateToolSpecs != null) {
|
||||
merged.addAll(operateToolSpecs);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1057,4 +1161,8 @@ public class AgentScopeReActRuntime implements AgentRuntime {
|
||||
ReActAgent getAgent() {
|
||||
return agent;
|
||||
}
|
||||
|
||||
private record AgentScopeToolkitBuildResult(Map<String, List<AgentTool>> skillTools,
|
||||
List<AgentToolSpec> operateToolSpecs) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user