Skip to content

Commit 7013a38

Browse files
authored
Feat/simulation update (#206)
* added new simulation runner to test agents in simulated environment * Delphi loop in ai congress * created example simulations * Added conditional intervention testing
1 parent 8e6da89 commit 7013a38

31 files changed

Lines changed: 12309 additions & 99 deletions
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
from __future__ import annotations
2+
3+
import logging
4+
5+
from forecasting_tools.agents_and_tools.ai_congress.congress_member_agent import (
6+
CongressMemberAgent,
7+
)
8+
from forecasting_tools.agents_and_tools.ai_congress.data_models import (
9+
CongressMember,
10+
ForecastDescription,
11+
PolicyProposal,
12+
)
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
def _make_member(
18+
name: str = "Test Member", ai_model: str = "test-model"
19+
) -> CongressMember:
20+
return CongressMember(
21+
name=name,
22+
role="Test Role",
23+
political_leaning="Center",
24+
general_motivation="Test motivation",
25+
expertise_areas=["economics", "policy evaluation"],
26+
personality_traits=["analytical", "pragmatic"],
27+
ai_model=ai_model,
28+
)
29+
30+
31+
def _make_forecast(
32+
footnote_id: int = 1,
33+
title: str = "Test Forecast",
34+
prediction: str = "65%",
35+
is_conditional: bool = False,
36+
) -> ForecastDescription:
37+
return ForecastDescription(
38+
footnote_id=footnote_id,
39+
question_title=title,
40+
question_text="Will X happen by 2027?",
41+
resolution_criteria="Resolves YES if X happens",
42+
prediction=prediction,
43+
reasoning="Based on historical data and trends",
44+
key_sources=["source1.com", "source2.com"],
45+
is_conditional=is_conditional,
46+
)
47+
48+
49+
def _make_proposal(
50+
member_name: str = "Member A",
51+
recommendations: list[str] | None = None,
52+
forecasts: list[ForecastDescription] | None = None,
53+
delphi_round: int = 1,
54+
) -> PolicyProposal:
55+
member = _make_member(name=member_name)
56+
if recommendations is None:
57+
recommendations = [
58+
f"Recommendation 1 from {member_name}",
59+
f"Recommendation 2 from {member_name}",
60+
]
61+
if forecasts is None:
62+
forecasts = [_make_forecast(footnote_id=1, title=f"{member_name} Forecast")]
63+
return PolicyProposal(
64+
member=member,
65+
research_summary=f"Research summary from {member_name}",
66+
decision_criteria=["Criterion 1", "Criterion 2"],
67+
forecasts=forecasts,
68+
proposal_markdown=f"# Proposal from {member_name}\n\nSome analysis text.",
69+
key_recommendations=recommendations,
70+
delphi_round=delphi_round,
71+
)
72+
73+
74+
class TestFormatOtherProposalsForReview:
75+
def test_formats_multiple_proposals(self) -> None:
76+
proposals = [
77+
_make_proposal("Alice"),
78+
_make_proposal("Bob"),
79+
]
80+
result = CongressMemberAgent._format_other_proposals_for_review(proposals)
81+
assert "Alice" in result
82+
assert "Bob" in result
83+
assert "Recommendation 1 from Alice" in result
84+
assert "Recommendation 1 from Bob" in result
85+
86+
def test_includes_forecast_titles_and_predictions(self) -> None:
87+
forecasts = [
88+
_make_forecast(footnote_id=1, title="GDP Growth", prediction="40%"),
89+
_make_forecast(footnote_id=2, title="Unemployment", prediction="25%"),
90+
]
91+
proposals = [_make_proposal("Carol", forecasts=forecasts)]
92+
result = CongressMemberAgent._format_other_proposals_for_review(proposals)
93+
assert "GDP Growth" in result
94+
assert "40%" in result
95+
assert "Unemployment" in result
96+
assert "25%" in result
97+
98+
def test_skips_proposals_without_member(self) -> None:
99+
proposal_without_member = PolicyProposal(
100+
member=None,
101+
research_summary="No member",
102+
decision_criteria=["C1"],
103+
forecasts=[],
104+
proposal_markdown="Text",
105+
key_recommendations=["Rec"],
106+
)
107+
result = CongressMemberAgent._format_other_proposals_for_review(
108+
[proposal_without_member]
109+
)
110+
assert result == ""
111+
112+
def test_separates_proposals_with_dividers(self) -> None:
113+
proposals = [_make_proposal("Alice"), _make_proposal("Bob")]
114+
result = CongressMemberAgent._format_other_proposals_for_review(proposals)
115+
assert "---" in result
116+
117+
118+
class TestFormatOwnProposalForReview:
119+
def test_formats_own_recommendations_and_forecasts(self) -> None:
120+
proposal = _make_proposal(
121+
"Self",
122+
recommendations=["My Rec 1", "My Rec 2"],
123+
forecasts=[_make_forecast(title="My Forecast", prediction="70%")],
124+
)
125+
result = CongressMemberAgent._format_own_proposal_for_review(proposal)
126+
assert "My Rec 1" in result
127+
assert "My Rec 2" in result
128+
assert "My Forecast" in result
129+
assert "70%" in result
130+
131+
132+
class TestBuildRevisionInstructions:
133+
def test_includes_member_identity(self) -> None:
134+
member = _make_member("TestAgent")
135+
agent = CongressMemberAgent(member)
136+
own_proposal = _make_proposal("TestAgent")
137+
other_proposals = [_make_proposal("Other1")]
138+
instructions = agent._build_revision_instructions(
139+
"Should we regulate AI?", own_proposal, other_proposals, delphi_round=2
140+
)
141+
assert "TestAgent" in instructions
142+
assert "Test Role" in instructions
143+
144+
def test_includes_policy_prompt(self) -> None:
145+
member = _make_member("TestAgent")
146+
agent = CongressMemberAgent(member)
147+
own_proposal = _make_proposal("TestAgent")
148+
other_proposals = [_make_proposal("Other1")]
149+
policy_prompt = "How should we address climate change?"
150+
instructions = agent._build_revision_instructions(
151+
policy_prompt, own_proposal, other_proposals, delphi_round=2
152+
)
153+
assert policy_prompt in instructions
154+
155+
def test_includes_delphi_round_number(self) -> None:
156+
member = _make_member("TestAgent")
157+
agent = CongressMemberAgent(member)
158+
own_proposal = _make_proposal("TestAgent")
159+
other_proposals = [_make_proposal("Other1")]
160+
instructions = agent._build_revision_instructions(
161+
"Test prompt", own_proposal, other_proposals, delphi_round=3
162+
)
163+
assert "Delphi Round 3" in instructions
164+
165+
def test_includes_other_members_proposals(self) -> None:
166+
member = _make_member("TestAgent")
167+
agent = CongressMemberAgent(member)
168+
own_proposal = _make_proposal("TestAgent")
169+
other_proposals = [
170+
_make_proposal("Rival1", recommendations=["Impose tariffs"]),
171+
_make_proposal("Rival2", recommendations=["Remove tariffs"]),
172+
]
173+
instructions = agent._build_revision_instructions(
174+
"Trade policy", own_proposal, other_proposals, delphi_round=2
175+
)
176+
assert "Rival1" in instructions
177+
assert "Rival2" in instructions
178+
assert "Impose tariffs" in instructions
179+
assert "Remove tariffs" in instructions
180+
181+
def test_includes_own_proposal_content(self) -> None:
182+
member = _make_member("TestAgent")
183+
agent = CongressMemberAgent(member)
184+
own_proposal = _make_proposal(
185+
"TestAgent", recommendations=["My special recommendation"]
186+
)
187+
other_proposals = [_make_proposal("Other")]
188+
instructions = agent._build_revision_instructions(
189+
"Test prompt", own_proposal, other_proposals, delphi_round=2
190+
)
191+
assert "My special recommendation" in instructions
192+
193+
194+
class TestOrchestratorProposalFiltering:
195+
def test_other_proposals_exclude_own_member(self) -> None:
196+
proposals = [
197+
_make_proposal("Alice"),
198+
_make_proposal("Bob"),
199+
_make_proposal("Carol"),
200+
]
201+
alice_others = [p for p in proposals if p.member and p.member.name != "Alice"]
202+
assert len(alice_others) == 2
203+
assert all(p.member.name != "Alice" for p in alice_others)
204+
assert {p.member.name for p in alice_others} == {"Bob", "Carol"}
205+
206+
def test_own_proposal_found_by_member_name(self) -> None:
207+
proposals = [
208+
_make_proposal("Alice"),
209+
_make_proposal("Bob"),
210+
_make_proposal("Carol"),
211+
]
212+
bob_own = next(
213+
(p for p in proposals if p.member and p.member.name == "Bob"),
214+
None,
215+
)
216+
assert bob_own is not None
217+
assert bob_own.member.name == "Bob"
218+
219+
def test_own_proposal_none_when_member_missing(self) -> None:
220+
proposals = [
221+
_make_proposal("Alice"),
222+
_make_proposal("Carol"),
223+
]
224+
bob_own = next(
225+
(p for p in proposals if p.member and p.member.name == "Bob"),
226+
None,
227+
)
228+
assert bob_own is None

0 commit comments

Comments
 (0)