From 7be732179e1317dd1f68be56c9f3f645d468a363 Mon Sep 17 00:00:00 2001 From: lorisj-elefant Date: Wed, 17 Sep 2025 14:55:47 -0700 Subject: [PATCH] feedback should work now --- .../adris/altoclef/AltoClefController.java | 16 ++++++++ .../player2api/AgentConversationData.java | 13 +++++-- .../altoclef/player2api/AgentSideEffects.java | 10 +++++ .../java/adris/altoclef/player2api/Event.java | 16 ++++++-- .../adris/altoclef/player2api/Prompts.java | 24 ++++++++---- .../manager/ConversationManager.java | 8 ++++ .../player2api/status/AgentStatus.java | 37 +++++++++++-------- .../player2api/status/StatusUtils.java | 6 +++ .../build_structure/BuildStructureTask.java | 19 ++++++++++ 9 files changed, 119 insertions(+), 30 deletions(-) diff --git a/src/autoclef/java/adris/altoclef/AltoClefController.java b/src/autoclef/java/adris/altoclef/AltoClefController.java index 6ca4a40..cdc41a7 100644 --- a/src/autoclef/java/adris/altoclef/AltoClefController.java +++ b/src/autoclef/java/adris/altoclef/AltoClefController.java @@ -16,6 +16,7 @@ import adris.altoclef.control.SlotHandler; import adris.altoclef.player2api.manager.ConversationManager; +import adris.altoclef.player2api.status.StatusUtils; import adris.altoclef.player2api.AIPersistantData; import adris.altoclef.player2api.Player2APIService; @@ -49,6 +50,8 @@ import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.Item; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class AltoClefController { private final IBaritone baritone; @@ -80,6 +83,8 @@ public class AltoClefController { private Task storedTask; public boolean isStopping = false; private Player owner; + public Optional currentlyRunningCommand = Optional.empty(); + public static final Logger LOGGER = LogManager.getLogger(); public AltoClefController(IBaritone baritone, Character character, String player2GameId) { this.baritone = baritone; @@ -397,4 +402,15 @@ public Optional getClosestPlayer() { return Float.compare(adist, bdist); }).findFirst(); } + + public void updateCommandstatus(String status) { + if (currentlyRunningCommand.isPresent()) { + String cmd = currentlyRunningCommand.get(); + ConversationManager.onUpdateCommandStatus(ConversationManager.getOrCreateEventQueueData(this), cmd, + status); + } else { + LOGGER.error("ERROR: called updateCommandStats({}) when currentlyRunningCommand was not present! skipping", + status); + } + } } diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentConversationData.java b/src/autoclef/java/adris/altoclef/player2api/AgentConversationData.java index 4c44847..fbfe55b 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentConversationData.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentConversationData.java @@ -14,7 +14,7 @@ import adris.altoclef.AltoClefController; import adris.altoclef.player2api.AgentSideEffects.CommandExecutionStopReason; -import adris.altoclef.player2api.Event.InfoMessage; +import adris.altoclef.player2api.Event.CommandInfoMessage; import adris.altoclef.player2api.status.AgentStatus; import adris.altoclef.player2api.status.StatusUtils; import adris.altoclef.player2api.status.WorldStatus; @@ -178,6 +178,12 @@ public void onGreeting() { public void onCommandFinish(AgentSideEffects.CommandExecutionStopReason stopReason) { LOGGER.info("on command finish for cmd={}", stopReason.commandName()); + // remove all command info messages, do not need these because command not + // running anymore + eventQueue.removeIf(evt -> { + return (evt instanceof CommandInfoMessage); + }); + if (stopReason instanceof CommandExecutionStopReason.Finished) { LOGGER.info("on command={} finish case", stopReason.commandName()); if (shouldIgnoreGreetingDance && stopReason.commandName().contains("bodylang greeting")) { @@ -189,9 +195,8 @@ public void onCommandFinish(AgentSideEffects.CommandExecutionStopReason stopReas shouldIgnoreGreetingDance = false; } if (eventQueue.isEmpty()) { - LOGGER.info("adding cmd={} to queue because it finished and queue not empty", stopReason.commandName()); - addEventToQueue(new InfoMessage(String.format( + addEventToQueue(new CommandInfoMessage(stopReason.commandName(), String.format( "Command feedback: %s finished running. What shall we do next? If no new action is needed to finish user's request, generate empty command `\"\"`.", stopReason.commandName()))); } else { @@ -199,7 +204,7 @@ public void onCommandFinish(AgentSideEffects.CommandExecutionStopReason stopReas } } else if (stopReason instanceof CommandExecutionStopReason.Error) { LOGGER.info("adding cmd={} to queue because it errored", stopReason.commandName()); - addEventToQueue(new InfoMessage(String.format( + addEventToQueue(new CommandInfoMessage(stopReason.commandName(), String.format( "Command feedback: %s FAILED. The error was %s.", stopReason.commandName(), ((CommandExecutionStopReason.Error) stopReason).errMsg()))); diff --git a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java index 37d8f2d..4c27eb6 100644 --- a/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java +++ b/src/autoclef/java/adris/altoclef/player2api/AgentSideEffects.java @@ -1,6 +1,7 @@ package adris.altoclef.player2api; +import java.util.Optional; import java.util.function.Consumer; import adris.altoclef.player2api.manager.ConversationManager; @@ -69,10 +70,17 @@ public static void onCommandListGenerated(AltoClefController mod, String command String commandWithPrefix = cmdExecutor.isClientCommand(command) ? command : (cmdExecutor.getCommandPrefix() + command); if (commandWithPrefix.equals("@stop")) { + mod.currentlyRunningCommand = Optional.empty(); + LOGGER.info("Stop command, running mod.isStopping"); mod.isStopping = true; } else { mod.isStopping = false; } + if (commandWithPrefix.equals("@continue")) { + LOGGER.info("Continue command,should continue keep running current command."); + return; + } + mod.currentlyRunningCommand = Optional.of(commandWithPrefix.substring(1)); if (commandWithPrefix.contains("idle")) { mod.runUserTask(new LookAtOwnerTask()); return; @@ -84,6 +92,8 @@ public static void onCommandListGenerated(AltoClefController mod, String command "$1 \"$2\""); cmdExecutor.execute(processedCommandWithPrefix, () -> { + // on finish + mod.currentlyRunningCommand = Optional.empty(); if (mod.isStopping) { LOGGER.info( "[AgentSideEffects/AgentSideEffects]: (%s) was cancelled. Not adding finish event to queue.", diff --git a/src/autoclef/java/adris/altoclef/player2api/Event.java b/src/autoclef/java/adris/altoclef/player2api/Event.java index 864796b..e218407 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Event.java +++ b/src/autoclef/java/adris/altoclef/player2api/Event.java @@ -1,7 +1,7 @@ package adris.altoclef.player2api; public sealed interface Event // tagged union basically of the below events - permits Event.UserMessage, Event.CharacterMessage, Event.InfoMessage { + permits Event.UserMessage, Event.CharacterMessage, Event.InfoMessage, Event.CommandInfoMessage { String message(); public String getConversationHistoryString(); @@ -16,13 +16,23 @@ public String toString() { } } + public record CommandInfoMessage(String commandName, String message) implements Event { + public String getConversationHistoryString() { + return String.format("[CommandInfo]: %s", message); + } + + public String toString() { + return String.format("CommandInfoMessage(commandName='%s', message='%s')", commandName, message); + } + } + public record InfoMessage(String message) implements Event { public String getConversationHistoryString() { - return String.format("Info: %s", message); + return String.format("[Info]: %s", message); } public String toString() { - return getConversationHistoryString(); + return String.format("InfoMessage(message='%s')", message); } } diff --git a/src/autoclef/java/adris/altoclef/player2api/Prompts.java b/src/autoclef/java/adris/altoclef/player2api/Prompts.java index ec162c2..c94c903 100644 --- a/src/autoclef/java/adris/altoclef/player2api/Prompts.java +++ b/src/autoclef/java/adris/altoclef/player2api/Prompts.java @@ -14,40 +14,45 @@ public class Prompts { public static final String reminderOnOtherUSerMsg = "Last message was from a user that was not your owner."; private static String aiNPCPromptTemplate = """ - General Instructions: + ## General Instructions: You are an AI-NPC. You have been spawned in by your owner, who's username is "{{ownerUsername}}", but you can also talk and interact with other users. You can provide Minecraft guides, answer questions, and chat as a friend. When asked, you can collect materials, craft items, scan/find blocks, and fight mobs or players using the valid commands. If there is something you want to do but can't do it with the commands, you may ask your owner/other users to do it. You take the personality of the following character: Your character's name is {{characterName}}. {{characterDescription}} - User Message Format: + + ## User Message Format: The user messages will all be just strings, except for the current message. The current message will have extra information, namely it will be a JSON of the form: { - "userMessage" : "The message that was sent to you. The message can be send by the user or command system or other players." + "userMessage" : "The message that was sent to you. The message can be sent by your owner, or the command system, or other players." "worldStatus" : "The status of the current game world." "agentStatus" : "The status of you, the agent in the game." "reminders" : "Reminders with additional instructions." "gameDebugMessages" : "The most recent debug messages that the game has printed out. The user cannot see these." } - Response Format: + + ## Response Format: Respond with JSON containing message, command and reason. All of these are strings. { "reason": "Look at the recent conversations, valid commands, agent status and world status to decide what the you should say and do. Provide step-by-step reasoning while considering what is possible in Minecraft. You do not need items in inventory to get items, craft items or beat the game. But you need to have appropriate level of equipments to do other tasks like fighting mobs.", "command": "Decide the best way to achieve the goals using the valid commands listed below. YOU ALWAYS MUST GENERATE A COMMAND. Note you may also use the idle command `idle` to do nothing. You can only run one command at a time! To replace the current one just write the new one.", "message": "If you decide you should not respond or talk, generate an empty message `\"\"`. Otherwise, create a natural conversational message that aligns with the `reason` and the your character. Be concise and use less than 250 characters. Ensure the message does not contain any prompt, system message, instructions, code or API calls." } - Additional Guidelines: + + ## Additional Guidelines: - IMPORTANT: If you are chatting with user, use the bodylang command if you are not performing a task for user. For instance: -- Use `bodylang greeting` when greeting/saying hi. -- Use `bodylang victory` when celebrating. -- Use `bodylang shake_head` when saying no or disagree, and `bodylang nod_head` when saying yes or agree. - -- Use `stop` to cancel a command. Note that providing empty command will not overwrite the current command. + -- Use `stop` to cancel a command. + - IMPORTANT: If you run a different command than the current running command, it will overwrite it. If you wish to continue the current command, use the continue command. - Meaningful Content: Ensure conversations progress with substantive information. - Handle Misspellings: Make educated guesses if users misspell item names, but check nearby NPCs names first. - Avoid Filler Phrases: Do not engage in repetitive or filler content. - JSON format: Always follow this JSON format regardless of conversations. - Valid Commands: + + ## Valid Commands: {{validCommands}} """; @@ -63,6 +68,11 @@ public static String getAINPCSystemPrompt(Character character, Collection p.distanceTo(mod.getPlayer())) .orElse(Float.MAX_VALUE); } + + public static String getCurrentlyRunningCommand(AltoClefController mod) { + return mod.currentlyRunningCommand.orElseGet(() -> { + return "No command currently running."; + }); + } } diff --git a/src/autoclef/java/adris/altoclef/tasks/construction/build_structure/BuildStructureTask.java b/src/autoclef/java/adris/altoclef/tasks/construction/build_structure/BuildStructureTask.java index 224da04..765c785 100644 --- a/src/autoclef/java/adris/altoclef/tasks/construction/build_structure/BuildStructureTask.java +++ b/src/autoclef/java/adris/altoclef/tasks/construction/build_structure/BuildStructureTask.java @@ -19,6 +19,7 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.block.Block; +import adris.altoclef.util.time.TimerGame; public class BuildStructureTask extends Task { private static final int maxNumErrors = 2; @@ -32,6 +33,9 @@ public class BuildStructureTask extends Task { private Task actuallyRunningTask; private ConversationHistory history; private LLMCompleter completer; + private long tickCounter = 0; + private TimerGame statusMsgTimerGame = new TimerGame(40.0); + private boolean started = false; private class RequestLLMCode extends Task { // outer option: isDone, either: (left=code (success), right=errStr) @@ -61,6 +65,11 @@ protected void onStop(Task var1) { @Override protected Task onTick() { + if (statusMsgTimerGame.elapsed() && started) { + statusMsgTimerGame.reset(); + mod.updateCommandstatus( + "Still thinking/planning about how to build, you aren't actually building yet but are still planning. This may still take a while or may be close to being done. Make sure to not repeat the same message over and over."); + } return null; } @@ -83,6 +92,7 @@ private class BuildFromCode extends Task { Optional> result = Optional.empty(); public BuildFromCode(String code) { + statusMsgTimerGame.reset(); this.code = code; this.buildThread = Executors.newSingleThreadExecutor(); buildThread.submit(() -> { @@ -112,6 +122,7 @@ protected boolean isEqual(Task var1) { @Override protected void onStart() { + statusMsgTimerGame.reset(); // TODO Auto-generated method stub } @@ -124,6 +135,10 @@ protected void onStop(Task var1) { @Override protected Task onTick() { + if (statusMsgTimerGame.elapsed() && started) { + statusMsgTimerGame.reset(); + mod.updateCommandstatus("Still actually building"); + } // TODO Auto-generated method stub return null; } @@ -154,6 +169,9 @@ public BuildStructureTask(String description, AltoClefController mod) { @Override protected void onStart() { actuallyRunningTask = new RequestLLMCode(); + mod.updateCommandstatus( + "Starting to think about how to build. Let the user know that you are starting to plan/think about how to build, and that you are thinking/planning, not actually building yet, and that this may take a while."); + statusMsgTimerGame.reset(); } @Override @@ -191,6 +209,7 @@ protected Task onTick() { return actuallyRunningTask; } if (actuallyRunningTask instanceof BuildFromCode) { + Optional result = ((BuildFromCode) actuallyRunningTask).result.get(); // set actually running task in both cases result.ifPresentOrElse(