fix(agents): stop debate openers from rebutting a nonexistent argument

- the first speaker in each debate round received an empty opponent response,
  yet the prompt demanded a rebuttal, so models fabricated the other side
- substitute an explicit opening marker when an opponent has not spoken, across
  all five debators (bull, bear, and the three risk analysts) #1176
This commit is contained in:
Yijia-Xiao
2026-08-30 06:18:59 +00:00
parent 9b98f09613
commit 539eae8fd6
7 changed files with 154 additions and 8 deletions

View File

@@ -1,6 +1,7 @@
from tradingagents.agents.utils.agent_utils import (
get_instrument_context_from_state,
get_language_instruction,
opponent_argument_or_opening,
)
@@ -10,7 +11,9 @@ def create_bear_researcher(llm):
history = investment_debate_state.get("history", "")
bear_history = investment_debate_state.get("bear_history", "")
current_response = investment_debate_state.get("current_response", "")
current_response = opponent_argument_or_opening(
investment_debate_state.get("current_response", ""), "bull analyst"
)
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]

View File

@@ -1,6 +1,7 @@
from tradingagents.agents.utils.agent_utils import (
get_instrument_context_from_state,
get_language_instruction,
opponent_argument_or_opening,
)
@@ -10,7 +11,9 @@ def create_bull_researcher(llm):
history = investment_debate_state.get("history", "")
bull_history = investment_debate_state.get("bull_history", "")
current_response = investment_debate_state.get("current_response", "")
current_response = opponent_argument_or_opening(
investment_debate_state.get("current_response", ""), "bear analyst"
)
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]

View File

@@ -1,6 +1,7 @@
from tradingagents.agents.utils.agent_utils import (
get_instrument_context_from_state,
get_language_instruction,
opponent_argument_or_opening,
)
@@ -10,8 +11,12 @@ def create_aggressive_debator(llm):
history = risk_debate_state.get("history", "")
aggressive_history = risk_debate_state.get("aggressive_history", "")
current_conservative_response = risk_debate_state.get("current_conservative_response", "")
current_neutral_response = risk_debate_state.get("current_neutral_response", "")
current_conservative_response = opponent_argument_or_opening(
risk_debate_state.get("current_conservative_response", ""), "conservative analyst"
)
current_neutral_response = opponent_argument_or_opening(
risk_debate_state.get("current_neutral_response", ""), "neutral analyst"
)
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]

View File

@@ -1,6 +1,7 @@
from tradingagents.agents.utils.agent_utils import (
get_instrument_context_from_state,
get_language_instruction,
opponent_argument_or_opening,
)
@@ -10,8 +11,12 @@ def create_conservative_debator(llm):
history = risk_debate_state.get("history", "")
conservative_history = risk_debate_state.get("conservative_history", "")
current_aggressive_response = risk_debate_state.get("current_aggressive_response", "")
current_neutral_response = risk_debate_state.get("current_neutral_response", "")
current_aggressive_response = opponent_argument_or_opening(
risk_debate_state.get("current_aggressive_response", ""), "aggressive analyst"
)
current_neutral_response = opponent_argument_or_opening(
risk_debate_state.get("current_neutral_response", ""), "neutral analyst"
)
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]

View File

@@ -1,6 +1,7 @@
from tradingagents.agents.utils.agent_utils import (
get_instrument_context_from_state,
get_language_instruction,
opponent_argument_or_opening,
)
@@ -10,8 +11,12 @@ def create_neutral_debator(llm):
history = risk_debate_state.get("history", "")
neutral_history = risk_debate_state.get("neutral_history", "")
current_aggressive_response = risk_debate_state.get("current_aggressive_response", "")
current_conservative_response = risk_debate_state.get("current_conservative_response", "")
current_aggressive_response = opponent_argument_or_opening(
risk_debate_state.get("current_aggressive_response", ""), "aggressive analyst"
)
current_conservative_response = opponent_argument_or_opening(
risk_debate_state.get("current_conservative_response", ""), "conservative analyst"
)
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]

View File

@@ -65,6 +65,20 @@ def get_language_instruction() -> str:
return f" Write your entire response in {lang}."
def opponent_argument_or_opening(text: str, opponent: str) -> str:
"""Opponent's latest argument, or an explicit opening marker when empty.
The first speaker in each debate round receives an empty opponent response;
interpolating it into a "refute the opponent" prompt makes the model
fabricate the other side's position. Returning a clear "has not spoken yet"
marker instead lets it open with its own case (#1176).
"""
text = (text or "").strip()
if text:
return text
return f"(The {opponent} has not spoken yet — open the debate with your own case.)"
def _clean_identity_value(value: Any) -> str | None:
"""Return a trimmed string, or None for empty / placeholder-ish values."""
if not isinstance(value, str):