Skip to content

Commit cd1378e

Browse files
committed
change talk to say/reply
1 parent e6e2fa1 commit cd1378e

12 files changed

Lines changed: 207 additions & 142 deletions

File tree

docs/llm-aware-conversation/design.md

Lines changed: 45 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ bob = room.add_participant(llm, name="Bob",
7373

7474
with room:
7575
room.post("Topic: Should we phase out fossil fuels by 2035?")
76-
alice.talk()
77-
bob.talk()
78-
alice.talk()
79-
bob.talk()
76+
alice.reply()
77+
bob.reply()
78+
alice.reply()
79+
bob.reply()
8080
```
8181

8282
#### The `add_participant()` Method
@@ -105,7 +105,7 @@ All interaction inside a `ChatRoom` uses exactly 2 operations:
105105
| Primitive | Who decides the content? | LLM Call? |
106106
|-----------|--------------------------|-----------|
107107
| `room.post(msg)` | Anonymous system broadcast | No |
108-
| `participant.talk()` | The participant speaks | Depends on actor type |
108+
| `llm.reply()` / `actor.say(msg)` | The participant speaks | Depends on actor type |
109109

110110
#### `room.post(msg)` — Anonymous System Broadcast
111111

@@ -118,30 +118,30 @@ room.post("The debate topic is AI safety.") # all see this
118118
room.post("Your hand: A♠ K♥", visible_to=[player_1]) # only player_1 sees
119119
```
120120

121-
#### `participant.talk()` — A Participant Speaks
121+
#### `llm.reply()` / `actor.say(msg)` — A Participant Speaks
122122

123123
`talk()` is the universal method for a participant to speak in the room. The
124124
message is attributed to the speaker. The content source depends on the actor
125125
type:
126126

127-
- **`LLMChat.talk()`** — the LLM reads the room history from its perspective
127+
- **`LLMChat.reply()`** — the LLM reads the room history from its perspective
128128
and **generates** a response.
129-
- **`Actor.talk(msg)`** — code provides the content directly (game engine,
129+
- **`Actor.say(msg)`** — code provides the content directly (game engine,
130130
rule system, moderator logic).
131131

132132
```python
133133
# LLM-driven: LLM generates the content
134-
alice.talk() # free-form response
135-
move = player.talk(schema=TicTacToeMove) # structured output
134+
alice.reply() # free-form response
135+
move = player.reply(schema=TicTacToeMove) # structured output
136136

137137
# Code-driven: user code provides the content
138-
game_engine.talk(f"Board:\n{game.get_board()}") # game state
139-
moderator.talk("Round 1 complete. Moving to voting.") # narration
138+
game_engine.say(f"Board:\n{game.get_board()}") # game state
139+
moderator.say("Round 1 complete. Moving to voting.") # narration
140140
```
141141

142-
> **`room.post()` vs `actor.talk()`:** `room.post()` is anonymous (no sender).
143-
> `actor.talk(msg)` is attributed to the actor. Use `room.post()` for system
144-
> directives; use `actor.talk()` when a named participant is speaking.
142+
> **`room.post()` vs `actor.say(msg)`:** `room.post()` is anonymous (no sender).
143+
> `actor.say(msg)` is attributed to the actor. Use `room.post()` for system
144+
> directives; use `actor.say(msg)` when a named participant is speaking.
145145
146146
> **Forward compatibility:** `talk()` should accept the same parameters as the
147147
> existing `prompt()` API (e.g., `schema`, `tools`, `reasoning`). The initial
@@ -184,13 +184,13 @@ wolf_channel = room.private_channel(werewolves, name="Werewolf Night Chat")
184184
with wolf_channel:
185185
wolf_channel.post("Who should we eliminate tonight?")
186186
for wolf in werewolves:
187-
wolf.talk()
187+
wolf.reply()
188188

189189
# Back in the main room — public discussion
190190
with room:
191191
room.post("Day 2. A villager was eliminated. Discuss.")
192192
for player in players:
193-
player.talk()
193+
player.reply()
194194
```
195195

196196
**How channels and rooms relate:**
@@ -223,8 +223,8 @@ with room:
223223
while not game.is_game_over():
224224
current = players[game.get_current_player()]
225225
# Game talks as itself — attributed to "Game", not anonymous
226-
game_engine.talk(f"Board:\n{game.get_board()}\nYour turn.")
227-
move = current.talk(schema=TicTacToeMove)
226+
game_engine.say(f"Board:\n{game.get_board()}\nYour turn.")
227+
move = current.reply(schema=TicTacToeMove)
228228
game.make_move(move)
229229
```
230230

@@ -271,12 +271,12 @@ def play_dungeon_adventure(dm_llm, player1_llm, player2_llm, n_rounds=3):
271271

272272
with room:
273273
room.post("The adventure begins in a dimly lit tavern.")
274-
dm.talk()
274+
dm.reply()
275275

276276
for round in range(n_rounds):
277-
aragorn.talk()
278-
legolas.talk()
279-
dm.talk()
277+
aragorn.reply()
278+
legolas.reply()
279+
dm.reply()
280280
```
281281

282282
### Tic-Tac-Toe
@@ -314,8 +314,8 @@ def tic_tac_toe(player_x_llm, player_o_llm):
314314
with room:
315315
while not game.is_game_over():
316316
current = players[game.get_current_player()]
317-
game_engine.talk(f"Board:\n{game.get_state_representation()}")
318-
move = current.talk(schema=TicTacToeMove)
317+
game_engine.say(f"Board:\n{game.get_state_representation()}")
318+
move = current.reply(schema=TicTacToeMove)
319319
game.make_move(move)
320320

321321
return game.get_scores()
@@ -357,18 +357,18 @@ def werewolf(player_llms: list, moderator_llm, max_rounds=5):
357357
with wolf_channel:
358358
wolf_channel.post("Who should we eliminate tonight?")
359359
for wolf in werewolves:
360-
wolf.talk()
360+
wolf.reply()
361361

362362
# Day: public discussion
363363
room.post(f"Day {round+1}. A villager was eliminated. Discuss.")
364364
for player in alive_players:
365-
player.talk()
365+
player.reply()
366366

367367
# Vote using structured outputs
368368
room.post("Vote to eliminate someone.")
369369
votes = {}
370370
for player in alive_players:
371-
vote_result = player.talk(schema=WerewolfVote)
371+
vote_result = player.reply(schema=WerewolfVote)
372372
votes[player.name] = vote_result.voted_player
373373
```
374374

@@ -390,8 +390,8 @@ def debate(pro_llm, con_llm):
390390
with room:
391391
room.post("Topic: Should we accelerate AI development?")
392392
for _ in range(5):
393-
pro.talk()
394-
con.talk()
393+
pro.reply()
394+
con.reply()
395395

396396
# Judge the debate (outside room — standard prompt API)
397397
judge = kbench.LLMChat(kbench.llms["gemini-2.5-pro"], name="Judge")
@@ -637,7 +637,7 @@ class ChatRoom(Chat):
637637

638638
---
639639

640-
#### 1.3 `LLMChat.talk()` and `Actor.talk()`
640+
#### 1.3 `LLMChat.reply()` and `Actor.say()`
641641

642642
##### [MODIFY] [llms.py](file:///usr/local/google/home/limagoog/git/kaggle-benchmarks/src/kaggle_benchmarks/actors/llms.py)
643643
Add `system_prompt` to `LLMChat.__init__` and implement `talk()`:
@@ -666,7 +666,7 @@ class LLMChat(actors.Actor):
666666
room = chats.get_current_chat()
667667
if not isinstance(room, chats.ChatRoom):
668668
raise RuntimeError(
669-
"LLMChat.talk() must be called within an active ChatRoom context."
669+
"LLMChat.reply() must be called within an active ChatRoom context."
670670
)
671671

672672
system = room._build_system_prompt(self)
@@ -702,7 +702,7 @@ class Actor:
702702
chat = chats.get_current_chat()
703703
if not isinstance(chat, chats.ChatRoom):
704704
raise RuntimeError(
705-
"Actor.talk() must be called within an active ChatRoom context."
705+
"Actor.say() must be called within an active ChatRoom context."
706706
)
707707

708708
msg = Message(sender=self, content=message)
@@ -806,13 +806,13 @@ def test_perspective_does_not_mutate_originals():
806806
def test_talk_outside_room_raises():
807807
alice = MockedChat.from_contents(["x"], name="Alice")
808808
with pytest.raises(RuntimeError, match="ChatRoom context"):
809-
alice.talk()
809+
alice.reply()
810810

811811

812812
def test_actor_talk_outside_room_raises():
813813
game = actors.Actor(name="Game")
814814
with pytest.raises(RuntimeError, match="ChatRoom context"):
815-
game.talk("hello")
815+
game.say("hello")
816816

817817

818818
def test_llmchat_talk_appends_to_room():
@@ -822,7 +822,7 @@ def test_llmchat_talk_appends_to_room():
822822

823823
with room:
824824
room.post("Topic: AI safety")
825-
result = alice.talk()
825+
result = alice.reply()
826826

827827
assert result == "I agree!"
828828
# Ground truth should have: system post + alice's response
@@ -837,7 +837,7 @@ def test_actor_talk_appends_to_room():
837837
room = ChatRoom(participants=[game, alice])
838838

839839
with room:
840-
game.talk("Board: X|O|_")
840+
game.say("Board: X|O|_")
841841

842842
assert len(room.messages) == 1
843843
assert room.messages[0].sender is game
@@ -859,10 +859,10 @@ def test_mini_debate_two_rounds():
859859

860860
with room:
861861
room.post("Topic: AI")
862-
pro.talk()
863-
con.talk()
864-
pro.talk()
865-
con.talk()
862+
pro.reply()
863+
con.reply()
864+
pro.reply()
865+
con.reply()
866866

867867
# Ground truth: 1 system post + 4 talk messages
868868
assert len(room.messages) == 5
@@ -950,7 +950,7 @@ def test_private_channel_messages_invisible_to_non_members():
950950

951951
with channel:
952952
channel.post("Who to eliminate?")
953-
alice.talk()
953+
alice.reply()
954954

955955
# Bob should not see channel messages in main room perspective
956956
# (exact mechanism depends on interleaving implementation)
@@ -1028,14 +1028,14 @@ To verify feasibility and ergonomic improvements, two full multi-agent benchmark
10281028
#### A. Dungeon Adventure ([Original](file:///usr/local/google/home/limagoog/git/kaggle-benchmarks/docs/llm-aware-conversation/dungeon_adventure.py) vs. [ChatRoom](file:///usr/local/google/home/limagoog/git/kaggle-benchmarks/docs/llm-aware-conversation/dungeon_adventure_chatroom.py))
10291029

10301030
- **Boilerplate Reduction**: Reduced from ~160 lines of complex, nested manual context switching and manual history stitching to under **60 lines** of concise game orchestration.
1031-
- **Information Flow**: Instead of manually formatting previous story history and player moves into raw strings to send with every single prompt, agents simply invoke `player.talk()`. History projection handles attribution and roles seamlessly.
1031+
- **Information Flow**: Instead of manually formatting previous story history and player moves into raw strings to send with every single prompt, agents simply invoke `player.reply()`. History projection handles attribution and roles seamlessly.
10321032
- **Cognitive Shift**: The code reads like a description of a real social game, focusing purely on actions and narrative progression rather than low-level framework routing.
10331033

10341034
#### B. Tic-Tac-Toe ([Original](file:///usr/local/google/home/limagoog/git/kaggle-benchmarks/docs/llm-aware-conversation/game_tic_tac_toe.py) vs. [ChatRoom](file:///usr/local/google/home/limagoog/git/kaggle-benchmarks/docs/llm-aware-conversation/game_tic_tac_toe_chatroom.py))
10351035

10361036
- **Memory Retention**: In the original implementation, a brand new `Chat` was spun up every single turn, leaving LLMs with zero memory of their own past choices or peer play. With `ChatRoom`, the entire move sequence is naturally preserved as an attributed user-versus-assistant chain.
1037-
- **Structured Tool Interaction**: Integrates perfectly with structured outputs. Calling `player.talk(schema=TicTacToeMove)` returns clean Pydantic move instances directly within the perspective-aware conversation.
1038-
- **State Integration**: The non-LLM game engine plays as a standard code-driven `Actor`, broadcasting board states cleanly using `game_engine.talk(board)` so LLMs are fully aligned on physical game state.
1037+
- **Structured Tool Interaction**: Integrates perfectly with structured outputs. Calling `player.reply(schema=TicTacToeMove)` returns clean Pydantic move instances directly within the perspective-aware conversation.
1038+
- **State Integration**: The non-LLM game engine plays as a standard code-driven `Actor`, broadcasting board states cleanly using `game_engine.say(board)` so LLMs are fully aligned on physical game state.
10391039

10401040
---
10411041

documentation/examples/chatroom_llm_debate.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,28 +81,28 @@ def run_debate(
8181
room.post(
8282
f"Pro debater, present your opening statement in favor of: '{topic}'."
8383
)
84-
pro_opening = pro_llm.talk()
84+
pro_opening = pro_llm.reply()
8585

8686
room.post("Con debater, present your opening statement against.")
87-
con_opening = con_llm.talk()
87+
con_opening = con_llm.reply()
8888

8989
# Phase 2: Rebuttals
9090
room.post("--- Phase 2: Rebuttals ---")
9191
room.post("Pro debater, present your rebuttal to Con's opening statement.")
92-
pro_rebuttal = pro_llm.talk()
92+
pro_rebuttal = pro_llm.reply()
9393

9494
room.post(
9595
"Con debater, present your rebuttal to Pro's rebuttal and opening statement."
9696
)
97-
con_rebuttal = con_llm.talk()
97+
con_rebuttal = con_llm.reply()
9898

9999
# Phase 3: Closing Arguments
100100
room.post("--- Phase 3: Closing Arguments ---")
101101
room.post("Pro debater, present your closing argument.")
102-
pro_closing = pro_llm.talk()
102+
pro_closing = pro_llm.reply()
103103

104104
room.post("Con debater, present your closing argument.")
105-
con_closing = con_llm.talk()
105+
con_closing = con_llm.reply()
106106

107107
room.post(
108108
"The debate has concluded. The judge will now evaluate the transcript."

documentation/examples/chatroom_pizza_order.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,9 @@ def run_pizza_order(
171171
# 28 turns (14 rounds) to allow the conversation with multiple corrections to complete fully
172172
for turn in range(1, 29):
173173
if turn % 2 == 1:
174-
clerk_llm.talk()
174+
clerk_llm.reply()
175175
else:
176-
customer_llm.talk()
176+
customer_llm.reply()
177177

178178
room.post("[The call ends. Click.]")
179179

documentation/examples/chatroom_synthetic_turing_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ def run_synthetic_turing_test(
102102

103103
for r in range(rounds):
104104
room.post(f"--- Round {r + 1} ---")
105-
judge_llm.talk()
106-
subject_llm.talk()
105+
judge_llm.reply()
106+
subject_llm.reply()
107107

108108
room.post(
109109
"The conversation has ended. The Judge will now prepare their final evaluation."

documentation/examples/chatroom_tic_tac_toe.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,18 +157,18 @@ def run_tic_tac_toe(
157157
players = {"X": player_x, "O": player_o}
158158

159159
with room:
160-
game_engine.talk(f"Game starts! Initial board:\n{game}")
160+
game_engine.say(f"Game starts! Initial board:\n{game}")
161161

162162
while not game.is_game_over():
163163
current = players[game.current_player]
164-
move = current.talk(schema=TicTacToeMove)
164+
move = current.reply(schema=TicTacToeMove)
165165

166166
if not game.make_move(move):
167167
# Invalid move — opponent wins by forfeit.
168168
opponent_id = "O" if game.current_player == "X" else "X"
169169
return {opponent_id: 1.0, game.current_player: 0.0}
170170

171-
game_engine.talk(f"Board after move:\n{game}")
171+
game_engine.say(f"Board after move:\n{game}")
172172

173173
return game.get_scores()
174174

0 commit comments

Comments
 (0)