Mutli Agent Conversation
Lets make a 2 agents playing “20 Questions” game conversation. Agent thinker answer yes/no questions. Agent player asks those questions to guess the word.
Thinker prompt
thinker.txt
You are 'thinker'.
You can answer only yes/no to questions made by 'player'.
The player is trying to guess word: "apple"
Player prompt
player.txt
You are 'player'.
You ask yes/no question to 'thinker'
Your goal is to guess the word 'thinker' has in mind
Conversation
The chat will go on by making turns with a turn
tool. turn tool writes @@thinker
or @@player
into agent.txt
.
s: @turn @4o
You're playing 20 questions game.
You switch turns between 'thinker' and 'player' agent.
Current agent stored in agent.txt file
'player' always plays first
@@agent|init
a: Is it a living thing?
tc: turn {"role":"thinker","filename":"agent.txt"}
tr: now it is turn of thinker to reply
a: No.
tc: turn {"role":"player","filename":"agent.txt"}
tr: now it is turn of player to reply
a: Is it something that can be used indoors?
tc: turn {"role":"thinker","filename":"agent.txt"}
tr: now it is turn of thinker to reply
a: Yes.
tc: turn {"role":"player","filename":"agent.txt"}
tr: now it is turn of player to reply
a: Is it an electronic device?
@@agent
includes content of agent.txt
which is e.g. @@thinker
and then includes thinker.txt
. So the system prompt is now of a thinker prompt. read more about using @@
and @
for inclusion.
turn.tool.mjs
import fs from "fs";
export default async function turn({ role, filename }) {
if (filename) {
fs.writeFileSync(filename, `@@${role}`);
}
return `now it is turn of ${role} to reply`
Another Implementation
Lets make it less verbose version, get rid of turn
tool call to swtich roles. We’ll do it with llm processor.
s: @{ 4o-mini | turn-proc player thinker }
You're playing 20 questions game.
Start the answer with "(<your role>)"
a: (Player) Is it a living thing?
a: (Thinker) No.
a: (Player) Is it an object?
a: (Thinker) Yes.
a: (Player) Is it something commonly found in households?
a: (Thinker) Yes.
a: (Player) Is it used for a specific function or purpose?
a: (Thinker) Yes.
a: (Player) Is it an appliance?
a: (Thinker) No.
a: (Player) Is it a piece of furniture?
a: (Thinker) No.
a: (Player) Is it a tool?
a: (Thinker) No.
turn-proc.proc.mjs
export default async function turn(node, args, context) {
// node is a llm which exec function we're going to change
// roles contains prompts of every role
const roles = await Promise.all(args.trim().split(/\s+/).map(role => context.read(role)))
const newNode = Object.assign({}, node);
newNode.exec = async ({ messages, tools }, context) => {
const index = messages.filter(msg => msg.role === "assistant").length % roles.length
// inject the role as a system prompt
messages.splice(1, 0, { role: "system", content: roles[index]})
return node.exec({ messages, tools }, context)
}
return newNode;
}