Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,22 +44,24 @@
/**
* Abstract base class for agents that support structured output generation.
*
* <p>This class provides the infrastructure for generating structured output using the
* {@code generate_response} tool pattern combined with StructuredOutputHook for flow control.
* <p>This class provides the infrastructure for generating structured output using the {@code
* generate_response} tool pattern combined with StructuredOutputHook for flow control.
*
* <p><b>Key Features:</b>
*
* <ul>
* <li>Automatic tool registration for structured output</li>
* <li>Schema validation before tool execution</li>
* <li>Memory compression after structured output completion</li>
* <li>Configurable reminder mode (TOOL_CHOICE or PROMPT)</li>
* <li>Automatic tool registration for structured output
* <li>Schema validation before tool execution
* <li>Memory compression after structured output completion
* <li>Configurable reminder mode (TOOL_CHOICE or PROMPT)
* </ul>
*
* <p><b>Subclass Requirements:</b>
*
* <ul>
* <li>Provide Toolkit via constructor</li>
* <li>Implement {@link #getMemory()} for memory access</li>
* <li>Implement {@link #buildGenerateOptions()} for model options</li>
* <li>Provide Toolkit via constructor
* <li>Implement {@link #getMemory()} for memory access
* <li>Implement {@link #buildGenerateOptions()} for model options
* </ul>
*/
public abstract class StructuredOutputCapableAgent extends AgentBase {
Expand All @@ -72,9 +74,7 @@ public abstract class StructuredOutputCapableAgent extends AgentBase {
protected final Toolkit toolkit;
protected final StructuredOutputReminder structuredOutputReminder;

/**
* Constructor with default reminder mode (TOOL_CHOICE).
*/
/** Constructor with default reminder mode (TOOL_CHOICE). */
protected StructuredOutputCapableAgent(
String name,
String description,
Expand All @@ -84,9 +84,7 @@ protected StructuredOutputCapableAgent(
this(name, description, checkRunning, hooks, toolkit, StructuredOutputReminder.TOOL_CHOICE);
}

/**
* Constructor with custom reminder mode.
*/
/** Constructor with custom reminder mode. */
protected StructuredOutputCapableAgent(
String name,
String description,
Expand All @@ -102,23 +100,15 @@ protected StructuredOutputCapableAgent(
: StructuredOutputReminder.TOOL_CHOICE;
}

/**
* Get the toolkit for tool operations.
*/
/** Get the toolkit for tool operations. */
public Toolkit getToolkit() {
return toolkit;
}

/**
* Get the memory for structured output hook.
* Subclasses must implement this.
*/
/** Get the memory for structured output hook. Subclasses must implement this. */
public abstract Memory getMemory();

/**
* Build generate options for model calls.
* Subclasses must implement this.
*/
/** Build generate options for model calls. Subclasses must implement this. */
protected abstract GenerateOptions buildGenerateOptions();

// ==================== Structured Output Implementation ====================
Expand All @@ -133,9 +123,7 @@ protected final Mono<Msg> doCall(List<Msg> msgs, JsonNode outputSchema) {
return executeWithStructuredOutput(msgs, null, outputSchema);
}

/**
* Execute with structured output using StructuredOutputHook.
*/
/** Execute with structured output using StructuredOutputHook. */
private Mono<Msg> executeWithStructuredOutput(
List<Msg> msgs, Class<?> targetClass, JsonNode schemaDesc) {

Expand Down Expand Up @@ -196,9 +184,7 @@ private Mono<Msg> executeWithStructuredOutput(
});
}

/**
* Create the structured output tool with validation.
*/
/** Create the structured output tool with validation. */
private AgentTool createStructuredOutputTool(
Map<String, Object> schema, Class<?> targetClass, JsonNode schemaDesc) {
return new AgentTool() {
Expand Down Expand Up @@ -244,7 +230,7 @@ public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
// Create response message
Msg responseMsg =
Msg.builder()
.name(getName())
.name(param.getAgent().getName())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent 名称代替generate_response,会不会影响其他地方哦

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里是项目自带的格式化工具,处理格式化返回,现在用的是格式化工具名,而不是agent名,应该是个Bug,会导致多agent的情况下,不知道是谁的消息

.role(MsgRole.ASSISTANT)
.content(TextBlock.builder().text(contentText).build())
.metadata(
Expand All @@ -268,9 +254,7 @@ public Mono<ToolResultBlock> callAsync(ToolCallParam param) {
};
}

/**
* Extract structured result from tool result message.
*/
/** Extract structured result from tool result message. */
private Msg extractStructuredResult(Msg hookResultMsg) {
if (hookResultMsg == null) {
return null;
Expand Down Expand Up @@ -310,9 +294,7 @@ private Msg extractResponseData(Msg responseMsg) {
return responseMsg;
}

/**
* Merge collected metadata (ChatUsage and ThinkingBlock) into the message.
*/
/** Merge collected metadata (ChatUsage and ThinkingBlock) into the message. */
private Msg mergeCollectedMetadata(Msg msg, ChatUsage chatUsage, ThinkingBlock thinking) {
// Merge ChatUsage into metadata
Map<String, Object> metadata =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public class WerewolfGameConfig {

// Game rules
public static final int MAX_ROUNDS = 30;
public static final int MAX_DISCUSSION_ROUNDS = 2;
public static final int MAX_DISCUSSION_ROUNDS = 1;

// Model configuration
public static final String DEFAULT_MODEL = "qwen-plus";
public static final String DEFAULT_MODEL = "qwen3-max";

private WerewolfGameConfig() {
// Utility class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@
import java.util.List;
import java.util.stream.Collectors;

/**
* Represents the current state of the Werewolf game.
*/
/** Represents the current state of the Werewolf game. */
public class GameState {
private final List<Player> allPlayers;
private final List<Player> seers;
Expand All @@ -33,9 +31,17 @@ public class GameState {
private Player lastPoisonedVictim;
private boolean lastVictimResurrected;

// First night jump candidate (悍跳候选人)
private Player jumpCandidate; // 第一夜狼人投票选出的悍跳候选人

// Sheriff election state
private Player sheriff; // 当前警长
private boolean speakOrderReversed; // 发言顺序是否逆序(警长决定)
private boolean sheriffKilledInNight; // 警长是否在夜间被杀(用于天亮后移交警徽)

/**
* Constructs a new GameState instance with the provided players.
* Special role players (seer, witch, hunter) are detected from the list.
* Constructs a new GameState instance with the provided players. Special role players (seer,
* witch, hunter) are detected from the list.
*
* @param allPlayers the list of all players participating in the game
*/
Expand All @@ -47,6 +53,11 @@ public GameState(List<Player> allPlayers) {
this.seers = findPlayersByRole(Role.SEER);
this.witches = findPlayersByRole(Role.WITCH);
this.hunters = findPlayersByRole(Role.HUNTER);

// Initialize sheriff state
this.sheriff = null;
this.speakOrderReversed = false;
this.sheriffKilledInNight = false;
}

private List<Player> findPlayersByRole(Role role) {
Expand Down Expand Up @@ -184,10 +195,53 @@ public boolean isLastVictimResurrected() {
return lastVictimResurrected;
}

/**
* Returns the current sheriff.
*
* @return the sheriff player, or null if none
*/
public Player getSheriff() {
return sheriff;
}

/**
* Indicates whether the speaking order is reversed.
*
* @return true if speaking order is reversed; false otherwise
*/
public boolean isSpeakOrderReversed() {
return speakOrderReversed;
}

/**
* Indicates whether the sheriff was killed during the night.
*
* @return true if sheriff was killed at night; false otherwise
*/
public boolean isSheriffKilledInNight() {
return sheriffKilledInNight;
}

/**
* Returns the jump candidate selected by werewolves on the first night.
*
* @return the jump candidate player, or null if none
*/
public Player getJumpCandidate() {
return jumpCandidate;
}

// State modifiers
/**
* Increments the round counter by one to start a new round.
* Sets the jump candidate selected by werewolves on the first night.
*
* @param jumpCandidate the werewolf player selected to jump as seer
*/
public void setJumpCandidate(Player jumpCandidate) {
this.jumpCandidate = jumpCandidate;
}

/** Increments the round counter by one to start a new round. */
public void nextRound() {
this.currentRound++;
}
Expand Down Expand Up @@ -220,32 +274,83 @@ public void setLastVictimResurrected(boolean resurrected) {
}

/**
* Clears last night results, including the werewolf victim, poisoned victim,
* and resurrection flag, preparing for the next night.
* Clears last night results, including the werewolf victim, poisoned victim, and resurrection
* flag, preparing for the next night.
*/
public void clearNightResults() {
this.lastNightVictim = null;
this.lastPoisonedVictim = null;
this.lastVictimResurrected = false;
}

/**
* Sets the sheriff.
*
* @param sheriff the new sheriff player
*/
public void setSheriff(Player sheriff) {
// Remove sheriff status from previous sheriff
if (this.sheriff != null) {
this.sheriff.setSheriff(false);
}
this.sheriff = sheriff;
if (sheriff != null) {
sheriff.setSheriff(true);
}
}

/**
* Sets whether the speaking order is reversed.
*
* @param reversed true for reversed order; false for normal order
*/
public void setSpeakOrderReversed(boolean reversed) {
this.speakOrderReversed = reversed;
}

/**
* Sets whether the sheriff was killed during the night.
*
* @param killed true if sheriff was killed at night; false otherwise
*/
public void setSheriffKilledInNight(boolean killed) {
this.sheriffKilledInNight = killed;
}

// Winning condition checks
/**
* Checks if werewolves meet the win condition.
* Werewolves win if they are alive and their count is greater than or equal to
* the number of alive villager-camp players.
* Checks if werewolves meet the win condition. Werewolves win if: 1. All villagers (ordinary
* villagers) are dead, OR 2. All god roles (seer, witch, hunter) are dead
*
* @return true if werewolves win; false otherwise
*/
public boolean checkWerewolvesWin() {
int aliveWerewolves = getAliveWerewolves().size();
int aliveVillagers = getAliveVillagers().size();
return aliveWerewolves > 0 && aliveWerewolves >= aliveVillagers;
if (aliveWerewolves == 0) {
return false;
}

// Check if all ordinary villagers are dead
boolean allVillagersDead =
getAlivePlayers().stream().noneMatch(p -> p.getRole() == Role.VILLAGER);

// Check if all god roles are dead
boolean allGodsDead =
getAlivePlayers().stream()
.filter(
p ->
p.getRole() == Role.SEER
|| p.getRole() == Role.WITCH
|| p.getRole() == Role.HUNTER)
.count()
== 0;

return allVillagersDead || allGodsDead;
}

/**
* Checks if villagers meet the win condition.
* Villagers win when all werewolves have been eliminated.
* Checks if villagers meet the win condition. Villagers win when all werewolves have been
* eliminated.
*
* @return true if villagers win; false otherwise
*/
Expand Down
Loading
Loading