refactor(action): remove the explicit PendingAction control flow and assume the confirmation semantics through a short-term, fast-decaying ContextBlock belonging to the ACTION domain.

This commit is contained in:
2026-03-24 22:28:42 +08:00
parent 82db27484c
commit 7879836b91
11 changed files with 128 additions and 516 deletions

View File

@@ -3,7 +3,9 @@ package work.slhaf.partner.core.action;
import lombok.NonNull; import lombok.NonNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import work.slhaf.partner.api.agent.factory.capability.annotation.Capability; import work.slhaf.partner.api.agent.factory.capability.annotation.Capability;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.ExecutableAction;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.action.runner.RunnerClient; import work.slhaf.partner.core.action.runner.RunnerClient;
@@ -20,20 +22,6 @@ public interface ActionCapability {
Set<ExecutableAction> listActions(@Nullable ExecutableAction.Status status, @Nullable String source); Set<ExecutableAction> listActions(@Nullable ExecutableAction.Status status, @Nullable String source);
PendingActionRecord createPendingAction(String userId, ExecutableAction executableAction, long ttlMillis, long reminderBeforeMillis);
List<PendingActionRecord> listActivePendingActions(String userId);
PendingActionRecord resolvePendingDecision(String userId, String pendingId, PendingActionRecord.Decision decision, String reason);
boolean markPendingReminded(String pendingId);
PendingActionRecord expirePendingIfWaiting(String pendingId);
void bindPendingLifecycleActions(String pendingId, StateAction reminderAction, StateAction expireAction);
void cancelPendingLifecycleActions(String pendingId);
List<String> selectTendencyCache(String input); List<String> selectTendencyCache(String input);
void updateTendencyCache(CacheAdjustData data); void updateTendencyCache(CacheAdjustData data);

View File

@@ -8,7 +8,9 @@ import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityCore
import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod; import work.slhaf.partner.api.agent.factory.capability.annotation.CapabilityMethod;
import work.slhaf.partner.common.vector.VectorClient; import work.slhaf.partner.common.vector.VectorClient;
import work.slhaf.partner.core.PartnerCore; import work.slhaf.partner.core.PartnerCore;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.ExecutableAction;
import work.slhaf.partner.core.action.entity.MetaAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.cache.ActionCacheData; import work.slhaf.partner.core.action.entity.cache.ActionCacheData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData;
@@ -48,11 +50,6 @@ public class ActionCore extends PartnerCore<ActionCore> {
* 持久行动池 * 持久行动池
*/ */
private CopyOnWriteArraySet<ExecutableAction> actionPool = new CopyOnWriteArraySet<>(); private CopyOnWriteArraySet<ExecutableAction> actionPool = new CopyOnWriteArraySet<>();
/**
* 待确认任务以userId区分不同用户因为需要跨请求确认
*/
private final HashMap<String, List<PendingActionRecord>> pendingActions = new HashMap<>();
private final HashMap<String, PendingLifecycleActions> pendingLifecycleActions = new HashMap<>();
/** /**
* 语义缓存与行为倾向映射 * 语义缓存与行为倾向映射
*/ */
@@ -88,132 +85,6 @@ public class ActionCore extends PartnerCore<ActionCore> {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@CapabilityMethod
public synchronized PendingActionRecord createPendingAction(String userId, ExecutableAction executableAction,
long ttlMillis, long reminderBeforeMillis) {
long now = System.currentTimeMillis();
long safeTtl = Math.max(ttlMillis, 1000L);
long safeReminderBefore = Math.max(0L, Math.min(reminderBeforeMillis, safeTtl - 1000L));
PendingActionRecord record = new PendingActionRecord();
record.setUserId(userId);
record.setExecutableAction(executableAction);
record.setCreatedAt(now);
record.setExpireAt(now + safeTtl);
record.setRemindAt(record.getExpireAt() - safeReminderBefore);
record.setStatus(PendingActionRecord.Status.WAITING_CONFIRM);
pendingActions.computeIfAbsent(userId, k -> new ArrayList<>()).add(record);
return record;
}
@CapabilityMethod
public synchronized List<PendingActionRecord> listActivePendingActions(String userId) {
List<PendingActionRecord> records = pendingActions.get(userId);
if (records == null || records.isEmpty()) {
return List.of();
}
return records.stream()
.filter(record -> record.getStatus() == PendingActionRecord.Status.WAITING_CONFIRM
|| record.getStatus() == PendingActionRecord.Status.REMINDER_SENT)
.toList();
}
@CapabilityMethod
public synchronized PendingActionRecord resolvePendingDecision(String userId, String pendingId,
PendingActionRecord.Decision decision, String reason) {
PendingActionRecord record = findPendingByUserAndId(userId, pendingId);
if (record == null) {
return null;
}
PendingActionRecord.Status status = record.getStatus();
boolean active = status == PendingActionRecord.Status.WAITING_CONFIRM
|| status == PendingActionRecord.Status.REMINDER_SENT;
if (!active) {
return record;
}
if (decision == PendingActionRecord.Decision.CONFIRM) {
record.setStatus(PendingActionRecord.Status.CONFIRMED);
record.setDecisionAt(System.currentTimeMillis());
record.setDecisionReason(reason);
cancelPendingLifecycleActions(pendingId);
return record;
}
if (decision == PendingActionRecord.Decision.REJECT) {
record.setStatus(PendingActionRecord.Status.REJECTED);
record.setDecisionAt(System.currentTimeMillis());
record.setDecisionReason(reason);
ExecutableAction executableAction = record.getExecutableAction();
executableAction.setStatus(ExecutableAction.Status.FAILED);
executableAction.setResult("行动被拒绝");
cancelPendingLifecycleActions(pendingId);
return record;
}
record.setDecisionReason(reason);
return record;
}
@CapabilityMethod
public synchronized boolean markPendingReminded(String pendingId) {
PendingActionRecord record = findPendingById(pendingId);
if (record == null) {
return false;
}
if (record.getStatus() != PendingActionRecord.Status.WAITING_CONFIRM) {
return false;
}
record.setStatus(PendingActionRecord.Status.REMINDER_SENT);
record.setReminded(true);
return true;
}
@CapabilityMethod
public synchronized PendingActionRecord expirePendingIfWaiting(String pendingId) {
PendingActionRecord record = findPendingById(pendingId);
if (record == null) {
return null;
}
PendingActionRecord.Status status = record.getStatus();
if (status != PendingActionRecord.Status.WAITING_CONFIRM
&& status != PendingActionRecord.Status.REMINDER_SENT) {
return null;
}
record.setStatus(PendingActionRecord.Status.EXPIRED);
record.setDecisionAt(System.currentTimeMillis());
record.setDecisionReason("等待确认超时");
cancelPendingLifecycleActions(pendingId);
return record;
}
@CapabilityMethod
public synchronized void bindPendingLifecycleActions(String pendingId, StateAction reminderAction, StateAction expireAction) {
PendingLifecycleActions old = pendingLifecycleActions.put(
pendingId, new PendingLifecycleActions(reminderAction, expireAction)
);
if (old != null) {
old.reminderAction.setEnabled(false);
old.expireAction.setEnabled(false);
}
PendingActionRecord record = findPendingById(pendingId);
if (record != null) {
record.setReminderActionId(reminderAction.getUuid());
record.setExpireActionId(expireAction.getUuid());
}
}
@CapabilityMethod
public synchronized void cancelPendingLifecycleActions(String pendingId) {
PendingLifecycleActions actions = pendingLifecycleActions.remove(pendingId);
if (actions == null) {
return;
}
actions.reminderAction.setEnabled(false);
actions.expireAction.setEnabled(false);
}
/** /**
* 计算输入内容的语义向量,根据与{@link ActionCacheData#getInputVector()}的相似度挑取缓存,后续将根据评估结果来更新计数 * 计算输入内容的语义向量,根据与{@link ActionCacheData#getInputVector()}的相似度挑取缓存,后续将根据评估结果来更新计数
* *
@@ -514,33 +385,6 @@ public class ActionCore extends PartnerCore<ActionCore> {
return "action-core"; return "action-core";
} }
private PendingActionRecord findPendingByUserAndId(String userId, String pendingId) {
List<PendingActionRecord> records = pendingActions.get(userId);
if (records == null) {
return null;
}
for (PendingActionRecord record : records) {
if (record.getPendingId().equals(pendingId)) {
return record;
}
}
return null;
}
private PendingActionRecord findPendingById(String pendingId) {
for (List<PendingActionRecord> records : pendingActions.values()) {
for (PendingActionRecord record : records) {
if (record.getPendingId().equals(pendingId)) {
return record;
}
}
}
return null;
}
private record PendingLifecycleActions(StateAction reminderAction, StateAction expireAction) {
}
public enum ExecutorType { public enum ExecutorType {
VIRTUAL, PLATFORM VIRTUAL, PLATFORM
} }

View File

@@ -1,39 +0,0 @@
package work.slhaf.partner.core.action.entity;
import lombok.Data;
import java.util.UUID;
@Data
public class PendingActionRecord {
private final String pendingId = UUID.randomUUID().toString();
private String userId;
private ExecutableAction executableAction;
private Status status = Status.WAITING_CONFIRM;
private long createdAt;
private long remindAt;
private long expireAt;
private boolean reminded;
private long decisionAt;
private String decisionReason;
private String reminderActionId;
private String expireActionId;
public enum Status {
WAITING_CONFIRM,
REMINDER_SENT,
CONFIRMED,
REJECTED,
EXPIRED
}
public enum Decision {
CONFIRM,
REJECT,
HOLD
}
}

View File

@@ -2,6 +2,8 @@ package work.slhaf.partner.module.modules.action.planner;
import kotlin.Unit; import kotlin.Unit;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability; import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule; import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.api.agent.factory.component.annotation.Init; import work.slhaf.partner.api.agent.factory.component.annotation.Init;
@@ -12,12 +14,10 @@ import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.*; import work.slhaf.partner.core.action.entity.*;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData; import work.slhaf.partner.core.action.entity.cache.CacheAdjustMetaData;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.module.modules.action.executor.ActionExecutor; import work.slhaf.partner.module.modules.action.executor.ActionExecutor;
import work.slhaf.partner.module.modules.action.planner.confirmer.ActionConfirmer;
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.ConfirmerInput;
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.ConfirmerResult;
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.PendingDecisionItem;
import work.slhaf.partner.module.modules.action.planner.evaluator.ActionEvaluator; import work.slhaf.partner.module.modules.action.planner.evaluator.ActionEvaluator;
import work.slhaf.partner.module.modules.action.planner.evaluator.entity.EvaluatorInput; import work.slhaf.partner.module.modules.action.planner.evaluator.entity.EvaluatorInput;
import work.slhaf.partner.module.modules.action.planner.evaluator.entity.EvaluatorResult; import work.slhaf.partner.module.modules.action.planner.evaluator.entity.EvaluatorResult;
@@ -26,11 +26,7 @@ import work.slhaf.partner.module.modules.action.planner.extractor.entity.Extract
import work.slhaf.partner.module.modules.action.scheduler.ActionScheduler; import work.slhaf.partner.module.modules.action.scheduler.ActionScheduler;
import work.slhaf.partner.runtime.interaction.data.context.PartnerRunningFlowContext; import work.slhaf.partner.runtime.interaction.data.context.PartnerRunningFlowContext;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -39,9 +35,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
*/ */
public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlowContext> { public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlowContext> {
private static final long PENDING_TTL_MILLIS = 30 * 60 * 1000L;
private static final long PENDING_REMINDER_ADVANCE_MILLIS = 5 * 60 * 1000L;
private static final String IMMEDIATE_WATCHER_CRON = "0/5 * * * * ?"; private static final String IMMEDIATE_WATCHER_CRON = "0/5 * * * * ?";
private static final String PENDING_BLOCK_SOURCE = "action_planner_pending";
private static final double PENDING_REPLACE_FADE_FACTOR = 100.0;
private static final double PENDING_TIME_FADE_FACTOR = 40.0;
private static final double PENDING_ACTIVATE_FACTOR = 0.0;
private final ActionAssemblyHelper assemblyHelper = new ActionAssemblyHelper(); private final ActionAssemblyHelper assemblyHelper = new ActionAssemblyHelper();
@@ -55,8 +53,6 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
@InjectModule @InjectModule
private ActionExtractor actionExtractor; private ActionExtractor actionExtractor;
@InjectModule @InjectModule
private ActionConfirmer actionConfirmer;
@InjectModule
private ActionScheduler actionScheduler; private ActionScheduler actionScheduler;
@InjectModule @InjectModule
private ActionExecutor actionExecutor; private ActionExecutor actionExecutor;
@@ -71,33 +67,17 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
@Override @Override
public void execute(@NotNull PartnerRunningFlowContext context) { public void execute(@NotNull PartnerRunningFlowContext context) {
try { try {
List<Callable<Void>> tasks = new ArrayList<>();
addConfirmTask(tasks, context);
addNewActionTask(tasks, context);
executor.invokeAll(tasks);
} catch (Exception e) {
log.error("执行异常", e);
}
}
/**
* 新的提取与评估任务
*
* @param tasks 并发任务列表
* @param context 流程上下文
*/
private void addNewActionTask(List<Callable<Void>> tasks, PartnerRunningFlowContext context) {
tasks.add(() -> {
ExtractorResult extractorResult = actionExtractor.execute(context.getInput()); ExtractorResult extractorResult = actionExtractor.execute(context.getInput());
if (extractorResult.getTendencies().isEmpty()) { if (extractorResult.getTendencies().isEmpty()) {
return null; return;
} }
EvaluatorInput evaluatorInput = assemblyHelper.buildEvaluatorInput(extractorResult); EvaluatorInput evaluatorInput = assemblyHelper.buildEvaluatorInput(extractorResult);
List<EvaluatorResult> evaluatorResults = actionEvaluator.execute(evaluatorInput); // 并发操作均为访问 List<EvaluatorResult> evaluatorResults = actionEvaluator.execute(evaluatorInput); // 并发操作均为访问
putActionData(evaluatorResults, context); putActionData(evaluatorResults, context);
updateTendencyCache(evaluatorResults, context.getInput(), extractorResult); updateTendencyCache(evaluatorResults, context.getInput(), extractorResult);
return null; } catch (Exception e) {
}); log.error("执行异常", e);
}
} }
private void updateTendencyCache(List<EvaluatorResult> evaluatorResults, String input, private void updateTendencyCache(List<EvaluatorResult> evaluatorResults, String input,
@@ -122,117 +102,114 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
}); });
} }
/**
* 待确认行动的判断任务
*
* @param tasks 并发任务列表
* @param context 流程上下文
*/
private void addConfirmTask(List<Callable<Void>> tasks, PartnerRunningFlowContext context) {
tasks.add(() -> {
ConfirmerInput confirmerInput = assemblyHelper.buildConfirmerInput(context);
ConfirmerResult result = actionConfirmer.execute(confirmerInput);
setupConfirmedActionInfo(context, result);
return null;
});
}
private void setupConfirmedActionInfo(PartnerRunningFlowContext context, ConfirmerResult result) {
List<PendingDecisionItem> decisions = result.getDecisions();
if (decisions == null || decisions.isEmpty()) {
return;
}
for (PendingDecisionItem decisionItem : decisions) {
PendingActionRecord pendingAction = actionCapability.resolvePendingDecision(
context.getSource(),
decisionItem.getPendingId(),
decisionItem.getDecision(),
decisionItem.getReason()
);
if (pendingAction == null) {
continue;
}
if (decisionItem.getDecision() == PendingActionRecord.Decision.CONFIRM
&& pendingAction.getStatus() == PendingActionRecord.Status.CONFIRMED) {
executeOrSchedule(pendingAction.getExecutableAction());
}
}
}
private void putActionData(List<EvaluatorResult> evaluatorResults, PartnerRunningFlowContext context) { private void putActionData(List<EvaluatorResult> evaluatorResults, PartnerRunningFlowContext context) {
for (EvaluatorResult evaluatorResult : evaluatorResults) { for (EvaluatorResult evaluatorResult : evaluatorResults) {
expireResolvedPending(evaluatorResult);
if (!evaluatorResult.isOk()) {
continue;
}
ExecutableAction executableAction = assemblyHelper.buildActionData(evaluatorResult, context.getSource()); ExecutableAction executableAction = assemblyHelper.buildActionData(evaluatorResult, context.getSource());
if (evaluatorResult.isNeedConfirm()) { if (evaluatorResult.isNeedConfirm()) {
PendingActionRecord pendingAction = actionCapability.createPendingAction( registerPendingContextBlock(executableAction, evaluatorResult);
context.getSource(), continue;
executableAction,
PENDING_TTL_MILLIS,
PENDING_REMINDER_ADVANCE_MILLIS
);
schedulePendingLifecycleActions(pendingAction);
} else {
executeOrSchedule(executableAction);
} }
executeOrSchedule(executableAction);
} }
} }
private void schedulePendingLifecycleActions(PendingActionRecord pendingAction) { private void expireResolvedPending(EvaluatorResult evaluatorResult) {
StateAction reminderAction = buildPendingReminderAction(pendingAction); EvaluatorResult.ResolvedPending resolvedPending = evaluatorResult.getResolvedPending();
StateAction expireAction = buildPendingExpireAction(pendingAction); if (resolvedPending == null) {
actionCapability.bindPendingLifecycleActions(pendingAction.getPendingId(), reminderAction, expireAction);
actionScheduler.schedule(reminderAction);
actionScheduler.schedule(expireAction);
}
private StateAction buildPendingReminderAction(PendingActionRecord pendingAction) {
return new StateAction(
pendingAction.getUserId(),
"pending-action-reminder:" + pendingAction.getPendingId(),
"待确认行动提醒",
Schedulable.ScheduleType.ONCE,
asScheduleContent(pendingAction.getRemindAt()),
new StateAction.Trigger.Call(() -> {
handlePendingReminder(pendingAction.getPendingId(), pendingAction.getUserId());
return Unit.INSTANCE;
})
);
}
private StateAction buildPendingExpireAction(PendingActionRecord pendingAction) {
return new StateAction(
pendingAction.getUserId(),
"pending-action-expire:" + pendingAction.getPendingId(),
"待确认行动失效",
Schedulable.ScheduleType.ONCE,
asScheduleContent(pendingAction.getExpireAt()),
new StateAction.Trigger.Call(() -> {
handlePendingExpire(pendingAction.getPendingId());
return Unit.INSTANCE;
})
);
}
private void handlePendingReminder(String pendingId, String userId) {
boolean marked = actionCapability.markPendingReminded(pendingId);
if (!marked) {
return; return;
} }
try { if (resolvedPending.getBlockName() == null || resolvedPending.getSource() == null) {
// TODO target 指定行为待补充; 主动回复链路待补充 return;
cognitionCapability.initiateTurn("系统提醒存在待确认行动即将过期请确认是否继续执行。pendingId=" + pendingId, userId);
} catch (Exception e) {
log.warn("触发待确认行动提醒失败, pendingId: {}", pendingId, e);
} }
cognitionCapability.contextWorkspace().expire(
resolvedPending.getBlockName(),
resolvedPending.getSource()
);
} }
private void handlePendingExpire(String pendingId) { private void registerPendingContextBlock(ExecutableAction executableAction, EvaluatorResult evaluatorResult) {
actionCapability.expirePendingIfWaiting(pendingId); String blockName = buildPendingBlockName(executableAction);
ContextBlock block = new ContextBlock(
buildPendingBlock(blockName, executableAction, evaluatorResult),
buildPendingCompactBlock(blockName, executableAction, evaluatorResult),
buildPendingAbstractBlock(blockName, executableAction, evaluatorResult),
Set.of(ContextBlock.VisibleDomain.ACTION),
PENDING_REPLACE_FADE_FACTOR,
PENDING_TIME_FADE_FACTOR,
PENDING_ACTIVATE_FACTOR
);
cognitionCapability.contextWorkspace().register(block);
} }
private String asScheduleContent(long targetTimeMillis) { private String buildPendingBlockName(ExecutableAction executableAction) {
long now = System.currentTimeMillis(); return "pending_action-" + executableAction.getUuid();
long safeTarget = Math.max(targetTimeMillis, now + 1000L); }
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(safeTarget), ZoneId.systemDefault()).toString();
private BlockContent buildPendingBlock(String blockName, ExecutableAction executableAction, EvaluatorResult evaluatorResult) {
return new BlockContent(blockName, PENDING_BLOCK_SOURCE) {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendTextElement(document, root, "state", "waiting_confirm");
appendTextElement(document, root, "action_uuid", executableAction.getUuid());
appendTextElement(document, root, "action_type", evaluatorResult.getType());
appendTextElement(document, root, "tendency", executableAction.getTendency());
appendTextElement(document, root, "reason", executableAction.getReason());
appendTextElement(document, root, "description", executableAction.getDescription());
appendTextElement(document, root, "source_user", executableAction.getSource());
if (evaluatorResult.getScheduleType() != null) {
appendTextElement(document, root, "schedule_type", evaluatorResult.getScheduleType());
}
if (evaluatorResult.getScheduleContent() != null) {
appendTextElement(document, root, "schedule_content", evaluatorResult.getScheduleContent());
}
Map<Integer, List<String>> primaryActionChain = evaluatorResult.getPrimaryActionChain();
if (primaryActionChain == null || primaryActionChain.isEmpty()) {
return;
}
Element chainRoot = document.createElement("primary_action_chain");
root.appendChild(chainRoot);
List<Integer> orders = new ArrayList<>(primaryActionChain.keySet());
orders.sort(Integer::compareTo);
for (Integer order : orders) {
Element orderElement = document.createElement("step");
orderElement.setAttribute("order", String.valueOf(order));
chainRoot.appendChild(orderElement);
appendRepeatedElements(
document,
orderElement,
"action_key",
primaryActionChain.getOrDefault(order, List.of())
);
}
}
};
}
private BlockContent buildPendingCompactBlock(String blockName, ExecutableAction executableAction, EvaluatorResult evaluatorResult) {
return new BlockContent(blockName, PENDING_BLOCK_SOURCE) {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendTextElement(document, root, "state", "waiting_confirm");
appendTextElement(document, root, "tendency", executableAction.getTendency());
appendTextElement(document, root, "description", executableAction.getDescription());
appendTextElement(document, root, "action_type", evaluatorResult.getType());
}
};
}
private BlockContent buildPendingAbstractBlock(String blockName, ExecutableAction executableAction, EvaluatorResult evaluatorResult) {
return new BlockContent(blockName, PENDING_BLOCK_SOURCE) {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendTextElement(document, root, "pending_tendency", executableAction.getTendency());
appendTextElement(document, root, "summary", "exists pending action waiting for confirmation");
appendTextElement(document, root, "action_type", evaluatorResult.getType());
}
};
} }
private void executeOrSchedule(ExecutableAction executableAction) { private void executeOrSchedule(ExecutableAction executableAction) {
@@ -403,13 +380,5 @@ public class ActionPlanner extends AbstractAgentModule.Running<PartnerRunningFlo
return existActions.containsAll(preActions); return existActions.containsAll(preActions);
} }
private ConfirmerInput buildConfirmerInput(PartnerRunningFlowContext context) {
ConfirmerInput confirmerInput = new ConfirmerInput();
confirmerInput.setInput(context.getInput());
List<PendingActionRecord> pendingActions = actionCapability.listActivePendingActions(context.getSource());
confirmerInput.setRecentMessages(cognitionCapability.snapshotChatMessages());
confirmerInput.setPendingActions(pendingActions);
return confirmerInput;
}
} }
} }

View File

@@ -1,124 +0,0 @@
package work.slhaf.partner.module.modules.action.planner.confirmer;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
import work.slhaf.partner.api.agent.factory.component.abstracts.AbstractAgentModule;
import work.slhaf.partner.api.agent.factory.component.abstracts.ActivateModel;
import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.ActionCore;
import work.slhaf.partner.core.action.entity.ExecutableAction;
import work.slhaf.partner.core.action.entity.PendingActionRecord;
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.ConfirmerInput;
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.ConfirmerResult;
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.PendingDecisionItem;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
public class ActionConfirmer extends AbstractAgentModule.Sub<ConfirmerInput, ConfirmerResult> implements ActivateModel {
@InjectCapability
private ActionCapability actionCapability;
@Override
public ConfirmerResult execute(ConfirmerInput data) {
List<PendingActionRecord> pendingActions = data.getPendingActions();
if (pendingActions == null || pendingActions.isEmpty()) {
return new ConfirmerResult();
}
ExecutorService executor = actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL);
CountDownLatch latch = new CountDownLatch(pendingActions.size());
ConfirmerResult result = new ConfirmerResult();
List<PendingDecisionItem> decisions = result.getDecisions();
for (PendingActionRecord pendingAction : pendingActions) {
executor.execute(() -> {
try {
ExecutableAction executableAction = pendingAction.getExecutableAction();
String prompt = buildPrompt(executableAction, data.getInput(), data.getRecentMessages());
DecisionResponse tempResult = formattedChat(
List.of(new Message(Message.Character.USER, prompt)),
DecisionResponse.class
);
PendingActionRecord.Decision decision = parseDecision(tempResult);
String reason = tempResult.getReason();
synchronized (decisions) {
decisions.add(new PendingDecisionItem(pendingAction.getPendingId(), decision, reason));
}
} catch (Exception e) {
synchronized (decisions) {
decisions.add(new PendingDecisionItem(
pendingAction.getPendingId(),
PendingActionRecord.Decision.HOLD,
"确认解析失败: " + e.getLocalizedMessage()
));
}
} finally {
latch.countDown();
}
});
}
try {
latch.await();
} catch (InterruptedException e) {
log.warn("CountDownLatch阻塞已中断");
}
return result;
}
private PendingActionRecord.Decision parseDecision(DecisionResponse tempResult) {
if (tempResult == null) {
return PendingActionRecord.Decision.HOLD;
}
String decisionText = tempResult.getDecision();
if (decisionText != null) {
String upperDecision = decisionText.toUpperCase();
if (upperDecision.contains("CONFIRM")) {
return PendingActionRecord.Decision.CONFIRM;
}
if (upperDecision.contains("REJECT")) {
return PendingActionRecord.Decision.REJECT;
}
if (upperDecision.contains("HOLD")) {
return PendingActionRecord.Decision.HOLD;
}
}
Boolean confirmed = tempResult.getConfirmed();
if (Boolean.TRUE.equals(confirmed)) {
return PendingActionRecord.Decision.CONFIRM;
}
return PendingActionRecord.Decision.HOLD;
}
private String buildPrompt(ExecutableAction data, String input, List<Message> recentMessages) {
JSONObject prompt = new JSONObject();
prompt.put("[用户输入]", input);
JSONObject actionData = prompt.putObject("[行动数据]");
actionData.put("[行动倾向]", data.getTendency());
actionData.put("[行动原因]", data.getReason());
actionData.put("[行动来源]", data.getSource());
actionData.put("[行动描述]", data.getDescription());
JSONArray decisionEnums = prompt.putArray("[决策选项]");
decisionEnums.add("CONFIRM");
decisionEnums.add("REJECT");
decisionEnums.add("HOLD");
JSONArray messageData = prompt.putArray("[近期对话]");
if (recentMessages != null) {
messageData.addAll(recentMessages);
}
return prompt.toString();
}
@Override
public String modelKey() {
return "action-confirmer";
}
@lombok.Data
private static class DecisionResponse {
private String decision;
private String reason;
private Boolean confirmed;
}
}

View File

@@ -1,14 +0,0 @@
package work.slhaf.partner.module.modules.action.planner.confirmer.entity;
import lombok.Data;
import work.slhaf.partner.api.chat.pojo.Message;
import work.slhaf.partner.core.action.entity.PendingActionRecord;
import java.util.List;
@Data
public class ConfirmerInput {
private String input;
private List<PendingActionRecord> pendingActions;
private List<Message> recentMessages;
}

View File

@@ -1,11 +0,0 @@
package work.slhaf.partner.module.modules.action.planner.confirmer.entity;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class ConfirmerResult {
private List<PendingDecisionItem> decisions = new ArrayList<>();
}

View File

@@ -1,15 +0,0 @@
package work.slhaf.partner.module.modules.action.planner.confirmer.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import work.slhaf.partner.core.action.entity.PendingActionRecord;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PendingDecisionItem {
private String pendingId;
private PendingActionRecord.Decision decision;
private String reason;
}

View File

@@ -53,7 +53,11 @@ public class ActionEvaluator extends AbstractAgentModule.Sub<EvaluatorInput, Lis
for (String tendency : tendencies) { for (String tendency : tendencies) {
list.add(() -> { list.add(() -> {
List<Message> messages = List.of( List<Message> messages = List.of(
cognitionCapability.contextWorkspace().resolve(List.of(ContextBlock.VisibleDomain.ACTION, ContextBlock.VisibleDomain.COGNITION, ContextBlock.VisibleDomain.MEMORY)).encodeToContextMessage(), cognitionCapability.contextWorkspace().resolve(List.of(
ContextBlock.VisibleDomain.ACTION,
ContextBlock.VisibleDomain.COGNITION,
ContextBlock.VisibleDomain.MEMORY
)).encodeToContextMessage(),
availableMetaActionContext(), availableMetaActionContext(),
new Message(Message.Character.USER, tendency) new Message(Message.Character.USER, tendency)
); );

View File

@@ -10,6 +10,7 @@ import java.util.Map;
public class EvaluatorResult { public class EvaluatorResult {
private boolean ok; private boolean ok;
private boolean needConfirm; private boolean needConfirm;
private ResolvedPending resolvedPending;
private ActionType type; private ActionType type;
private String scheduleContent; private String scheduleContent;
private SchedulableExecutableAction.ScheduleType scheduleType; private SchedulableExecutableAction.ScheduleType scheduleType;
@@ -21,4 +22,10 @@ public class EvaluatorResult {
public enum ActionType { public enum ActionType {
IMMEDIATE, PLANNING IMMEDIATE, PLANNING
} }
@Data
public static class ResolvedPending {
private String blockName;
private String source;
}
} }

View File

@@ -29,7 +29,10 @@ public class ActionExtractor extends AbstractAgentModule.Sub<String, ExtractorRe
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
try { try {
List<Message> messages = List.of( List<Message> messages = List.of(
cognitionCapability.contextWorkspace().resolve(List.of(ContextBlock.VisibleDomain.COGNITION)).encodeToContextMessage(), cognitionCapability.contextWorkspace().resolve(List.of(
ContextBlock.VisibleDomain.ACTION,
ContextBlock.VisibleDomain.COGNITION
)).encodeToContextMessage(),
new Message(Message.Character.USER, input) new Message(Message.Character.USER, input)
); );
return formattedChat( return formattedChat(
@@ -37,7 +40,7 @@ public class ActionExtractor extends AbstractAgentModule.Sub<String, ExtractorRe
ExtractorResult.class ExtractorResult.class
); );
} catch (Exception e) { } catch (Exception e) {
log.error("[ActionExtractor] 提取信息出错", e); log.error("提取信息出错", e);
} }
} }
return new ExtractorResult(); return new ExtractorResult();