refactor(action): add resume/interrupt methods to ExecutableAction; add related definitions to support acquiring help from user

This commit is contained in:
2026-03-23 17:24:19 +08:00
parent 617daea17c
commit 8cd12f7379
2 changed files with 164 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
package work.slhaf.partner.core.action.entity package work.slhaf.partner.core.action.entity
import work.slhaf.partner.module.modules.action.executor.entity.HistoryAction import work.slhaf.partner.module.modules.action.executor.entity.HistoryAction
import java.time.Instant
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.* import java.util.*
import kotlin.time.Duration import kotlin.time.Duration
@@ -112,6 +113,27 @@ sealed class ExecutableAction : Action() {
val additionalContext: MutableMap<Int, MutableList<String>> = mutableMapOf() val additionalContext: MutableMap<Int, MutableList<String>> = mutableMapOf()
override val timeout: Duration = 10.minutes override val timeout: Duration = 10.minutes
/**
* @param timeout 最长打断时间
* @return 是否超时结束
*/
fun interrupt(timeout: Int): Boolean {
status = Status.INTERRUPTED
val interruptAt = Instant.now().epochSecond
while (status == Status.INTERRUPTED) {
Thread.sleep(500);
if (Instant.now().epochSecond - interruptAt > timeout) {
return false
}
}
return true;
}
fun resume() {
status = Status.EXECUTING
}
} }
/** /**

View File

@@ -2,13 +2,21 @@ package work.slhaf.partner.module.modules.action.builtin;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
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.annotation.AgentComponent; import work.slhaf.partner.api.agent.factory.component.annotation.AgentComponent;
import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.action.ActionCapability;
import work.slhaf.partner.core.action.entity.Action;
import work.slhaf.partner.core.action.entity.ExecutableAction; import work.slhaf.partner.core.action.entity.ExecutableAction;
import work.slhaf.partner.core.action.entity.MetaActionInfo; import work.slhaf.partner.core.action.entity.MetaActionInfo;
import work.slhaf.partner.core.action.entity.intervention.InterventionType; import work.slhaf.partner.core.action.entity.intervention.InterventionType;
import work.slhaf.partner.core.action.entity.intervention.MetaIntervention; import work.slhaf.partner.core.action.entity.intervention.MetaIntervention;
import work.slhaf.partner.core.cognition.BlockContent;
import work.slhaf.partner.core.cognition.CognitionCapability;
import work.slhaf.partner.core.cognition.ContextBlock;
import work.slhaf.partner.core.cognition.ContextWorkspace;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
@@ -24,16 +32,145 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider {
@InjectCapability @InjectCapability
private ActionCapability actionCapability; private ActionCapability actionCapability;
@InjectCapability
private CognitionCapability cognitionCapability;
@Override @Override
public List<BuiltinActionRegistry.BuiltinActionDefinition> provideBuiltinActions() { public List<BuiltinActionRegistry.BuiltinActionDefinition> provideBuiltinActions() {
return List.of( return List.of(
buildCreateInterventionDefinition(), buildCreateInterventionDefinition(),
buildShowAvailableMetaActionsDefinition(), buildShowAvailableMetaActionsDefinition(),
buildShowIntervenableActionsDefinition() buildShowIntervenableActionsDefinition(),
buildAcquireInterventionDefinition(),
buildResumeInterruptedActionDefinition()
); );
} }
private BuiltinActionRegistry.BuiltinActionDefinition buildResumeInterruptedActionDefinition() {
Set<String> tags = new HashSet<>(basicTags);
tags.add("Agent Turn");
tags.add("Action Management");
MetaActionInfo info = new MetaActionInfo(
false,
null,
Map.of(
"actionId", "Uuid of the interrupted executable action to resume."
),
"Resume action that is interrupted.",
tags,
Set.of(),
Set.of(),
false,
JSONObject.of(
"result", "Plain text resume result, describes whether it is succeed or fail reason."
)
);
Function<Map<String, Object>, String> invoker = params -> {
String actionId = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "actionId");
try {
ExecutableAction executableAction = getExecutableAction(actionId);
executableAction.resume();
return "Resume succeed";
} catch (Exception e) {
return "Failed to resume action[" + actionId + "], reason: " + e.getLocalizedMessage();
}
};
return new BuiltinActionRegistry.BuiltinActionDefinition(
createActionKey("resume_interrupted_action"),
info,
invoker
);
}
/**
* 尝试向指定用户请求干预,通过自对话通道
*
* @return 内建 MetaAction 定义数据
*/
private BuiltinActionRegistry.BuiltinActionDefinition buildAcquireInterventionDefinition() {
Set<String> tags = new HashSet<>(basicTags);
tags.add("Agent Turn");
tags.add("Action Management");
MetaActionInfo info = new MetaActionInfo(
true,
null,
Map.of(
"actionId", "Uuid of the executing action that should enter intervention flow.",
"actionInfo", "Readable summary of the current action, used to explain the intervention context to the target.",
"demand", "What feedback, decision or operation is required from the target user.",
"target", "Target user or channel identifier that should receive the intervention request.",
"input", "Prompt content used to initiate the intervention turn toward the target.",
"timeout", "Maximum wait time for the interruption result, in the unit expected by ExecutableAction.interrupt(timeout)."
),
"Try to acquire the target user to intervene this Action.",
tags,
Set.of(),
Set.of(),
false,
JSONObject.of(
"result", "Plain text intervention status. It describes whether the target answered before timeout, or returns an error reason when the turn/interruption flow fails."
)
);
Function<Map<String, Object>, String> invoker = params -> {
String actionId = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "actionId").trim();
String actionInfo = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "actionInfo").trim();
String demand = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "demand").trim();
String target = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "target").trim();
String input = BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, "input").trim();
int timeout = BuiltinActionRegistry.BuiltinActionDefinition.requireInt(params, "timeout");
ContextWorkspace contextWorkspace = cognitionCapability.contextWorkspace();
String blockName = "acquire_intervention-" + actionId;
String source = "action_executor";
contextWorkspace.register(new ContextBlock(
new BlockContent(blockName, source) {
@Override
protected void fillXml(@NotNull Document document, @NotNull Element root) {
appendTextElement(document, root, "action_id", actionId);
appendTextElement(document, root, "action_info", actionInfo);
appendTextElement(document, root, "demand", demand);
}
},
Set.of(ContextBlock.VisibleDomain.ACTION, ContextBlock.VisibleDomain.COMMUNICATION),
10,
10,
20
));
try {
ExecutableAction executableAction = getExecutableAction(actionId);
cognitionCapability.initiateTurn(input, target);
boolean normal = executableAction.interrupt(timeout);
return normal ? target + "not answered" : target + "answered";
} catch (Exception e) {
return "Error happened while calling turn: " + e.getLocalizedMessage();
} finally {
contextWorkspace.expire(blockName, source);
}
};
return new BuiltinActionRegistry.BuiltinActionDefinition(
createActionKey("acquire_intervention"),
info,
invoker
);
}
private ExecutableAction getExecutableAction(String actionId) {
return actionCapability.listActions(Action.Status.EXECUTING, null)
.stream()
.filter(action -> action.getUuid().equals(actionId))
.findFirst()
.orElseThrow();
}
/** /**
* 用于展示当前已存在的可被干预的行动 * 用于展示当前已存在的可被干预的行动
* *
@@ -175,6 +312,10 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider {
intervention.setActions(actions); intervention.setActions(actions);
actionCapability.handleInterventions(List.of(intervention), target); actionCapability.handleInterventions(List.of(intervention), target);
ExecutableAction executableAction = getExecutableAction(targetId);
executableAction.resume();
return JSONObject.of("ok", true).toJSONString(); return JSONObject.of("ok", true).toJSONString();
}; };