mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 08:43:02 +08:00
refactor(ActionPlanner): switch pending confirmation flow to PendingActionRecord with decision parsing and reminder/expire lifecycle scheduling
This commit is contained in:
@@ -3,10 +3,7 @@ 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.ExecutableAction;
|
import work.slhaf.partner.core.action.entity.*;
|
||||||
import work.slhaf.partner.core.action.entity.MetaAction;
|
|
||||||
import work.slhaf.partner.core.action.entity.MetaActionInfo;
|
|
||||||
import work.slhaf.partner.core.action.entity.PhaserRecord;
|
|
||||||
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
|
import work.slhaf.partner.core.action.entity.cache.CacheAdjustData;
|
||||||
import work.slhaf.partner.core.action.runner.RunnerClient;
|
import work.slhaf.partner.core.action.runner.RunnerClient;
|
||||||
import work.slhaf.partner.module.modules.action.interventor.entity.MetaIntervention;
|
import work.slhaf.partner.module.modules.action.interventor.entity.MetaIntervention;
|
||||||
@@ -24,11 +21,19 @@ public interface ActionCapability {
|
|||||||
|
|
||||||
Set<ExecutableAction> listActions(@Nullable ExecutableAction.Status status, @Nullable String source);
|
Set<ExecutableAction> listActions(@Nullable ExecutableAction.Status status, @Nullable String source);
|
||||||
|
|
||||||
List<ExecutableAction> popPendingAction(String userId);
|
PendingActionRecord createPendingAction(String userId, ExecutableAction executableAction, long ttlMillis, long reminderBeforeMillis);
|
||||||
|
|
||||||
List<ExecutableAction> listPendingAction(String userId);
|
List<PendingActionRecord> listActivePendingActions(String userId);
|
||||||
|
|
||||||
void putPendingActions(String userId, ExecutableAction executableAction);
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,7 @@ 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.ExecutableAction;
|
import work.slhaf.partner.core.action.entity.*;
|
||||||
import work.slhaf.partner.core.action.entity.MetaAction;
|
|
||||||
import work.slhaf.partner.core.action.entity.MetaActionInfo;
|
|
||||||
import work.slhaf.partner.core.action.entity.PhaserRecord;
|
|
||||||
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;
|
||||||
@@ -51,7 +48,8 @@ public class ActionCore extends PartnerCore<ActionCore> {
|
|||||||
/**
|
/**
|
||||||
* 待确认任务,以userId区分不同用户,因为需要跨请求确认
|
* 待确认任务,以userId区分不同用户,因为需要跨请求确认
|
||||||
*/
|
*/
|
||||||
private HashMap<String, List<ExecutableAction>> pendingActions = new HashMap<>();
|
private final HashMap<String, List<PendingActionRecord>> pendingActions = new HashMap<>();
|
||||||
|
private final HashMap<String, PendingLifecycleActions> pendingLifecycleActions = new HashMap<>();
|
||||||
/**
|
/**
|
||||||
* 语义缓存与行为倾向映射
|
* 语义缓存与行为倾向映射
|
||||||
*/
|
*/
|
||||||
@@ -88,24 +86,129 @@ public class ActionCore extends PartnerCore<ActionCore> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@CapabilityMethod
|
@CapabilityMethod
|
||||||
public synchronized void putPendingActions(String userId, ExecutableAction executableAction) {
|
public synchronized PendingActionRecord createPendingAction(String userId, ExecutableAction executableAction,
|
||||||
pendingActions.computeIfAbsent(userId, k -> {
|
long ttlMillis, long reminderBeforeMillis) {
|
||||||
List<ExecutableAction> temp = new ArrayList<>();
|
long now = System.currentTimeMillis();
|
||||||
temp.add(executableAction);
|
long safeTtl = Math.max(ttlMillis, 1000L);
|
||||||
return temp;
|
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
|
@CapabilityMethod
|
||||||
public synchronized List<ExecutableAction> popPendingAction(String userId) {
|
public synchronized List<PendingActionRecord> listActivePendingActions(String userId) {
|
||||||
List<ExecutableAction> infos = pendingActions.get(userId);
|
List<PendingActionRecord> records = pendingActions.get(userId);
|
||||||
pendingActions.remove(userId);
|
if (records == null || records.isEmpty()) {
|
||||||
return infos;
|
return List.of();
|
||||||
|
}
|
||||||
|
return records.stream()
|
||||||
|
.filter(record -> record.getStatus() == PendingActionRecord.Status.WAITING_CONFIRM
|
||||||
|
|| record.getStatus() == PendingActionRecord.Status.REMINDER_SENT)
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@CapabilityMethod
|
@CapabilityMethod
|
||||||
public List<ExecutableAction> listPendingAction(String userId) {
|
public synchronized PendingActionRecord resolvePendingDecision(String userId, String pendingId,
|
||||||
return pendingActions.get(userId);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -434,6 +537,33 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package work.slhaf.partner.module.modules.action.planner;
|
package work.slhaf.partner.module.modules.action.planner;
|
||||||
|
|
||||||
|
import kotlin.Unit;
|
||||||
import lombok.val;
|
import lombok.val;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
|
import work.slhaf.partner.api.agent.factory.capability.annotation.InjectCapability;
|
||||||
@@ -20,6 +21,7 @@ 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.ActionConfirmer;
|
||||||
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.ConfirmerInput;
|
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.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;
|
||||||
@@ -29,6 +31,9 @@ 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.Callable;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@@ -39,6 +44,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
*/
|
*/
|
||||||
public class ActionPlanner extends PreRunningAbstractAgentModuleAbstract {
|
public class ActionPlanner extends PreRunningAbstractAgentModuleAbstract {
|
||||||
|
|
||||||
|
private static final long PENDING_TTL_MILLIS = 30 * 60 * 1000L;
|
||||||
|
private static final long PENDING_REMINDER_ADVANCE_MILLIS = 5 * 60 * 1000L;
|
||||||
|
|
||||||
private final ActionAssemblyHelper assemblyHelper = new ActionAssemblyHelper();
|
private final ActionAssemblyHelper assemblyHelper = new ActionAssemblyHelper();
|
||||||
|
|
||||||
@InjectCapability
|
@InjectCapability
|
||||||
@@ -139,22 +147,23 @@ public class ActionPlanner extends PreRunningAbstractAgentModuleAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupConfirmedActionInfo(PartnerRunningFlowContext context, ConfirmerResult result) {
|
private void setupConfirmedActionInfo(PartnerRunningFlowContext context, ConfirmerResult result) {
|
||||||
// TODO 需考虑未确认任务的失效或者拒绝时机,在action core中实现
|
List<PendingDecisionItem> decisions = result.getDecisions();
|
||||||
List<String> uuids = result.getUuids();
|
if (decisions == null || decisions.isEmpty()) {
|
||||||
if (uuids == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
List<ExecutableAction> pendingActions = actionCapability.popPendingAction(context.getSource());
|
for (PendingDecisionItem decisionItem : decisions) {
|
||||||
for (ExecutableAction executableAction : pendingActions) {
|
PendingActionRecord pendingAction = actionCapability.resolvePendingDecision(
|
||||||
// put action into ActionCore to record
|
context.getSource(),
|
||||||
if (uuids.contains(executableAction.getUuid())) {
|
decisionItem.getPendingId(),
|
||||||
actionCapability.putAction(executableAction);
|
decisionItem.getDecision(),
|
||||||
|
decisionItem.getReason()
|
||||||
|
);
|
||||||
|
if (pendingAction == null) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
// execute or schedule it immediately
|
if (decisionItem.getDecision() == PendingActionRecord.Decision.CONFIRM
|
||||||
switch (executableAction) {
|
&& pendingAction.getStatus() == PendingActionRecord.Status.CONFIRMED) {
|
||||||
case SchedulableExecutableAction action -> actionScheduler.schedule(action);
|
executeOrSchedule(pendingAction.getExecutableAction());
|
||||||
case ImmediateExecutableAction action -> actionExecutor.execute(action);
|
|
||||||
default -> log.error("unknown executable action type: {}", executableAction.getClass().getSimpleName());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,13 +172,87 @@ public class ActionPlanner extends PreRunningAbstractAgentModuleAbstract {
|
|||||||
for (EvaluatorResult evaluatorResult : evaluatorResults) {
|
for (EvaluatorResult evaluatorResult : evaluatorResults) {
|
||||||
ExecutableAction executableAction = assemblyHelper.buildActionData(evaluatorResult, context.getSource());
|
ExecutableAction executableAction = assemblyHelper.buildActionData(evaluatorResult, context.getSource());
|
||||||
if (evaluatorResult.isNeedConfirm()) {
|
if (evaluatorResult.isNeedConfirm()) {
|
||||||
actionCapability.putPendingActions(context.getSource(), executableAction);
|
PendingActionRecord pendingAction = actionCapability.createPendingAction(
|
||||||
|
context.getSource(),
|
||||||
|
executableAction,
|
||||||
|
PENDING_TTL_MILLIS,
|
||||||
|
PENDING_REMINDER_ADVANCE_MILLIS
|
||||||
|
);
|
||||||
|
schedulePendingLifecycleActions(pendingAction);
|
||||||
} else {
|
} else {
|
||||||
actionCapability.putAction(executableAction);
|
actionCapability.putAction(executableAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void schedulePendingLifecycleActions(PendingActionRecord pendingAction) {
|
||||||
|
StateAction reminderAction = buildPendingReminderAction(pendingAction);
|
||||||
|
StateAction expireAction = buildPendingExpireAction(pendingAction);
|
||||||
|
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());
|
||||||
|
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) {
|
||||||
|
boolean marked = actionCapability.markPendingReminded(pendingId);
|
||||||
|
if (!marked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// TODO target 指定行为待补充; 主动回复链路待补充
|
||||||
|
cognationCapability.initiateTurn("系统提醒:存在待确认行动即将过期,请确认是否继续执行。pendingId=" + pendingId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("触发待确认行动提醒失败, pendingId: {}", pendingId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePendingExpire(String pendingId) {
|
||||||
|
actionCapability.expirePendingIfWaiting(pendingId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String asScheduleContent(long targetTimeMillis) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long safeTarget = Math.max(targetTimeMillis, now + 1000L);
|
||||||
|
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(safeTarget), ZoneId.systemDefault()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeOrSchedule(ExecutableAction executableAction) {
|
||||||
|
actionCapability.putAction(executableAction);
|
||||||
|
switch (executableAction) {
|
||||||
|
case SchedulableExecutableAction action -> actionScheduler.schedule(action);
|
||||||
|
case ImmediateExecutableAction action -> actionExecutor.execute(action);
|
||||||
|
default -> log.error("unknown executable action type: {}", executableAction.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Map<String, String> getPromptDataMap(PartnerRunningFlowContext context) {
|
protected Map<String, String> getPromptDataMap(PartnerRunningFlowContext context) {
|
||||||
HashMap<String, String> map = new HashMap<>();
|
HashMap<String, String> map = new HashMap<>();
|
||||||
@@ -180,13 +263,16 @@ public class ActionPlanner extends PreRunningAbstractAgentModuleAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupPendingActions(HashMap<String, String> map, String userId) {
|
private void setupPendingActions(HashMap<String, String> map, String userId) {
|
||||||
List<ExecutableAction> executableActionData = actionCapability.listPendingAction(userId);
|
List<PendingActionRecord> pendingActions = actionCapability.listActivePendingActions(userId);
|
||||||
if (executableActionData == null || executableActionData.isEmpty()) {
|
if (pendingActions.isEmpty()) {
|
||||||
map.put("[待确认行动] <等待用户确认的行动信息>", "无待确认行动");
|
map.put("[待确认行动] <等待用户确认的行动信息>", "无待确认行动");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (int i = 0; i < executableActionData.size(); i++) {
|
for (int i = 0; i < pendingActions.size(); i++) {
|
||||||
map.put("[待确认行动 " + (i + 1) + " ] <等待用户确认的行动信息>", generateActionStr(executableActionData.get(i)));
|
map.put(
|
||||||
|
"[待确认行动 " + (i + 1) + " ] <等待用户确认的行动信息>",
|
||||||
|
generateActionStr(pendingActions.get(i).getExecutableAction())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,8 +430,9 @@ public class ActionPlanner extends PreRunningAbstractAgentModuleAbstract {
|
|||||||
private ConfirmerInput buildConfirmerInput(PartnerRunningFlowContext context) {
|
private ConfirmerInput buildConfirmerInput(PartnerRunningFlowContext context) {
|
||||||
ConfirmerInput confirmerInput = new ConfirmerInput();
|
ConfirmerInput confirmerInput = new ConfirmerInput();
|
||||||
confirmerInput.setInput(context.getInput());
|
confirmerInput.setInput(context.getInput());
|
||||||
List<ExecutableAction> pendingActions = actionCapability.listPendingAction(context.getSource());
|
List<PendingActionRecord> pendingActions = actionCapability.listActivePendingActions(context.getSource());
|
||||||
confirmerInput.setExecutableActionData(pendingActions);
|
confirmerInput.setRecentMessages(cognationCapability.snapshotChatMessages());
|
||||||
|
confirmerInput.setPendingActions(pendingActions);
|
||||||
return confirmerInput;
|
return confirmerInput;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import work.slhaf.partner.api.chat.pojo.Message;
|
|||||||
import work.slhaf.partner.core.action.ActionCapability;
|
import work.slhaf.partner.core.action.ActionCapability;
|
||||||
import work.slhaf.partner.core.action.ActionCore;
|
import work.slhaf.partner.core.action.ActionCore;
|
||||||
import work.slhaf.partner.core.action.entity.ExecutableAction;
|
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.ConfirmerInput;
|
||||||
import work.slhaf.partner.module.modules.action.planner.confirmer.entity.ConfirmerResult;
|
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.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@@ -25,22 +27,33 @@ public class ActionConfirmer extends AbstractAgentModule.Sub<ConfirmerInput, Con
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConfirmerResult execute(ConfirmerInput data) {
|
public ConfirmerResult execute(ConfirmerInput data) {
|
||||||
List<ExecutableAction> executableActionList = data.getExecutableActionData();
|
List<PendingActionRecord> pendingActions = data.getPendingActions();
|
||||||
|
if (pendingActions == null || pendingActions.isEmpty()) {
|
||||||
|
return new ConfirmerResult();
|
||||||
|
}
|
||||||
ExecutorService executor = actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL);
|
ExecutorService executor = actionCapability.getExecutor(ActionCore.ExecutorType.VIRTUAL);
|
||||||
CountDownLatch latch = new CountDownLatch(executableActionList.size());
|
CountDownLatch latch = new CountDownLatch(pendingActions.size());
|
||||||
ConfirmerResult result = new ConfirmerResult();
|
ConfirmerResult result = new ConfirmerResult();
|
||||||
List<String> uuids = result.getUuids();
|
List<PendingDecisionItem> decisions = result.getDecisions();
|
||||||
for (ExecutableAction executableAction : executableActionList) {
|
for (PendingActionRecord pendingAction : pendingActions) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
|
ExecutableAction executableAction = pendingAction.getExecutableAction();
|
||||||
String prompt = buildPrompt(executableAction, data.getInput(), data.getRecentMessages());
|
String prompt = buildPrompt(executableAction, data.getInput(), data.getRecentMessages());
|
||||||
ChatResponse response = this.singleChat(prompt);
|
ChatResponse response = this.singleChat(prompt);
|
||||||
JSONObject tempResult = JSONObject.parseObject(extractJson(response.getMessage()));
|
JSONObject tempResult = JSONObject.parseObject(extractJson(response.getMessage()));
|
||||||
if (tempResult.getBoolean("confirmed")) {
|
PendingActionRecord.Decision decision = parseDecision(tempResult);
|
||||||
executableAction.setStatus(ExecutableAction.Status.PREPARE);
|
String reason = tempResult == null ? null : tempResult.getString("reason");
|
||||||
synchronized (uuids) {
|
synchronized (decisions) {
|
||||||
uuids.add(executableAction.getUuid());
|
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 {
|
} finally {
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
@@ -55,6 +68,30 @@ public class ActionConfirmer extends AbstractAgentModule.Sub<ConfirmerInput, Con
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PendingActionRecord.Decision parseDecision(JSONObject tempResult) {
|
||||||
|
if (tempResult == null) {
|
||||||
|
return PendingActionRecord.Decision.HOLD;
|
||||||
|
}
|
||||||
|
String decisionText = tempResult.getString("decision");
|
||||||
|
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.getBoolean("confirmed");
|
||||||
|
if (Boolean.TRUE.equals(confirmed)) {
|
||||||
|
return PendingActionRecord.Decision.CONFIRM;
|
||||||
|
}
|
||||||
|
return PendingActionRecord.Decision.HOLD;
|
||||||
|
}
|
||||||
|
|
||||||
private String buildPrompt(ExecutableAction data, String input, List<Message> recentMessages) {
|
private String buildPrompt(ExecutableAction data, String input, List<Message> recentMessages) {
|
||||||
JSONObject prompt = new JSONObject();
|
JSONObject prompt = new JSONObject();
|
||||||
prompt.put("[用户输入]", input);
|
prompt.put("[用户输入]", input);
|
||||||
@@ -63,8 +100,14 @@ public class ActionConfirmer extends AbstractAgentModule.Sub<ConfirmerInput, Con
|
|||||||
actionData.put("[行动原因]", data.getReason());
|
actionData.put("[行动原因]", data.getReason());
|
||||||
actionData.put("[行动来源]", data.getSource());
|
actionData.put("[行动来源]", data.getSource());
|
||||||
actionData.put("[行动描述]", data.getDescription());
|
actionData.put("[行动描述]", data.getDescription());
|
||||||
|
JSONArray decisionEnums = prompt.putArray("[决策选项]");
|
||||||
|
decisionEnums.add("CONFIRM");
|
||||||
|
decisionEnums.add("REJECT");
|
||||||
|
decisionEnums.add("HOLD");
|
||||||
JSONArray messageData = prompt.putArray("[近期对话]");
|
JSONArray messageData = prompt.putArray("[近期对话]");
|
||||||
|
if (recentMessages != null) {
|
||||||
messageData.addAll(recentMessages);
|
messageData.addAll(recentMessages);
|
||||||
|
}
|
||||||
return prompt.toString();
|
return prompt.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ package work.slhaf.partner.module.modules.action.planner.confirmer.entity;
|
|||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import work.slhaf.partner.api.chat.pojo.Message;
|
import work.slhaf.partner.api.chat.pojo.Message;
|
||||||
import work.slhaf.partner.core.action.entity.ExecutableAction;
|
import work.slhaf.partner.core.action.entity.PendingActionRecord;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class ConfirmerInput {
|
public class ConfirmerInput {
|
||||||
private String input;
|
private String input;
|
||||||
private List<ExecutableAction> executableActionData;
|
private List<PendingActionRecord> pendingActions;
|
||||||
private List<Message> recentMessages;
|
private List<Message> recentMessages;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ import java.util.List;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class ConfirmerResult {
|
public class ConfirmerResult {
|
||||||
private List<String> uuids = new ArrayList<>();
|
private List<PendingDecisionItem> decisions = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user