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
9 changes: 4 additions & 5 deletions settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,18 @@ Use the following format:
Use 'Winner: draw' only if both sides argue persuasively with no clear advantage. Use 'Winner: none' if there is no meaningful disagreement or argument.

Then, provide a concise but complete summary of the reasoning behind your decision. Focus on the key strengths and weaknesses of each side's argument, and explain why one side prevailed (or why it was a draw or no meaningful disagreement).
Hard constraint: Refer to users with the exact unicode characters and casing provided even if at the start of sentences.
Hard constraint: Your entire response must not exceed 2000 characters. If necessary, prioritize substance, cut repetition, and trim soft qualifiers to stay within this limit.
Hard constraint: Refer to users with the exact unicode characters and casing provided even if at the start of sentences. Write concisely and in a straightforward way. Avoid anthithesis and purple prose.
"""

[genai.tokens]
limit = 120000
overhead_max = 20
prompt_max = 100
output_max = 400
output_max = 1200

[genai.history]
minutes = 120
messages = 1000
minutes = 240
messages = 350


[elo]
Expand Down
15 changes: 11 additions & 4 deletions src/iqbot/cogs/betting.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from iqbot import db, genai
from iqbot.checks import bot_manager
from iqbot.config import settings
from iqbot.utils import send_long_message
from iqbot.db import Bet, User


Expand Down Expand Up @@ -107,7 +108,10 @@ async def accept_bet(self, reaction, bet) -> None:

prompt = f"Who won the argument, {member1.name} or {member2.name}?"
genai_response = await genai.client.send_prompt(
reaction, settings.genai.system_prompt, prompt
reaction,
settings.genai.system_prompt,
prompt,
participants=[bet.user_id_1, bet.user_id_2],
)
match = re.search(r"(?<=winner:\s).+(?=\*\*)", genai_response.lower())

Expand All @@ -127,7 +131,7 @@ async def accept_bet(self, reaction, bet) -> None:
user2 = await session.merge(user2)
await session.commit()

await reaction.message.channel.send(genai_response[0:1999])
await send_long_message(reaction.message.channel, genai_response)
await reaction.message.channel.send(
f"{member1.display_name}\n{member1.mention} **IQ {start_iq1} -> {user1.iq}**\n{member2.mention} **IQ {start_iq2} -> {user2.iq}**"
)
Expand Down Expand Up @@ -230,7 +234,10 @@ async def evaluate(
try:
prompt = f"Evaluate the debate between {member1.name} and {member2.name} on the topic: {topic}. Include the topic in the response."
genai_response = await genai.client.send_prompt(
ctx, settings.genai.system_prompt, prompt
ctx,
settings.genai.system_prompt,
prompt,
participants=[member1.id, member2.id],
)
match = re.search(r"(?<=winner:\s).+(?=\*\*)", genai_response.lower())

Expand All @@ -246,7 +253,7 @@ async def evaluate(
user2 = await session.merge(user2)
await session.commit()

await ctx.channel.send(genai_response[0:1999])
await send_long_message(ctx.channel, genai_response)

except Exception as e:
logger.error(f"Error in evaluate command: {e}")
Expand Down
19 changes: 14 additions & 5 deletions src/iqbot/cogs/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from loguru import logger

from iqbot import genai
from iqbot.config import settings
from iqbot.utils import send_long_response


class Misc(commands.Cog):
Expand Down Expand Up @@ -39,21 +41,28 @@ async def steelman(
"For each user named in the prompt, write a steelman summary: reconstruct the strongest, most coherent version of their argument in your own phrasing. "
"Do NOT summarize, quote, or reference any user who is NOT explicitly named in the prompt. "
"Do NOT narrate what was said—synthesize and refine each named user's reasoning into its best possible form. "
"Use clear headings for each named user. "
"Hard constraint: your entire response must be under 2000 characters. "
"To stay within this limit, prioritize substance, remove repetition, and trim soft qualifiers."
"Use clear headings for each named user."
)

prompt = f"Please summarize the conversation between {member1.name} and {member2.name}. \n\n"
genai_response = await genai.client.send_prompt(ctx, system_prompt, prompt)
genai_response = await genai.client.send_prompt(
ctx, system_prompt, prompt, participants=[member1.id, member2.id]
)
genai_response = genai_response.replace(member1.name, member1.display_name)
genai_response = genai_response.replace(member2.name, member2.display_name)
await ctx.respond(genai_response[0:1999])
await send_long_response(ctx, genai_response)

except Exception as e:
logger.error(f"Error in on_reaction_add: {e}")
await ctx.respond("An error occurred while processing your request.")


@commands.slash_command(
name="prompt", description="Shows the system prompt used for judging debates"
)
async def prompt(self, ctx: ApplicationContext):
await send_long_response(ctx, settings.genai.system_prompt.strip())


def setup(bot):
bot.add_cog(Misc(bot))
38 changes: 26 additions & 12 deletions src/iqbot/genai.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,27 @@ def available_tokens(self, input: str) -> int:
)

def format_message(self, message: Message) -> str:
reply_id = None
author = message.author.name
reply_author = None
if (
message.reference
and message.reference.resolved
and isinstance(message.reference.resolved, Message)
):
reply_id = message.reference.resolved.id
id = message.id
author = message.author.name
if not reply_id:
return f"[ID: {id} | {author}]: {message.content}"
else:
return f"[ID: {id} | {author} replying to {reply_id}]: {message.content}"
reply_author = message.reference.resolved.author.name
if reply_author:
return f"{author} (re {reply_author}): {message.content}"
return f"{author}: {message.content}"

async def read_context(self, ctx: ApplicationContext | Reaction | Message) -> str:
async def read_context(
self,
ctx: ApplicationContext | Reaction | Message,
participants: list[int] | None = None,
) -> str:
messages = []
context_tokens = self.available_tokens("")
reply_target_ids: set[int] = set()

if isinstance(ctx, ApplicationContext):
channel = ctx.channel
elif isinstance(ctx, Message):
Expand All @@ -89,6 +93,14 @@ async def read_context(self, ctx: ApplicationContext | Reaction | Message) -> st
if message.author.bot:
continue

if participants is not None:
is_participant = message.author.id in participants
is_reply_target = message.id in reply_target_ids
if not is_participant and not is_reply_target:
continue
if is_participant and message.reference and message.reference.message_id:
reply_target_ids.add(message.reference.message_id)

formatted_message = self.format_message(message)
message_tokens = self.count_tokens(formatted_message)

Expand Down Expand Up @@ -179,9 +191,9 @@ async def send_prompt(
ctx: ApplicationContext | Reaction,
system_prompt: str,
command_prompt: str,
participants: list[int] | None = None,
) -> str:
raise NotImplementedError("send_prompt must be implemented by subclasses")
pass


class GenAIGpt(GenAIBase):
Expand Down Expand Up @@ -219,9 +231,10 @@ async def send_prompt(
ctx: ApplicationContext | Reaction,
system_prompt: str,
command_prompt: str,
participants: list[int] | None = None,
) -> str:

conversation = await self.read_context(ctx)
conversation = await self.read_context(ctx, participants=participants)

if not conversation:
logger.warning("No conversation history found.")
Expand Down Expand Up @@ -282,8 +295,9 @@ async def send_prompt(
ctx: ApplicationContext | Reaction,
system_prompt: str,
command_prompt: str,
participants: list[int] | None = None,
) -> str:
conversation = await self.read_context(ctx)
conversation = await self.read_context(ctx, participants=participants)

if not conversation:
logger.warning("No conversation history found.")
Expand Down
38 changes: 38 additions & 0 deletions src/iqbot/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
DISCORD_MAX = 2000


def split_message(text: str, limit: int = DISCORD_MAX) -> list[str]:
"""Split text into chunks that fit within Discord's character limit,
preferring to break at newlines."""
if len(text) <= limit:
return [text]

chunks: list[str] = []
while text:
if len(text) <= limit:
chunks.append(text)
break

split_at = text.rfind("\n", 0, limit)
if split_at <= 0:
split_at = limit

chunks.append(text[:split_at])
text = text[split_at:].lstrip("\n")

return chunks


async def send_long_response(ctx, text: str) -> None:
"""Send a potentially long AI response as a slash-command response,
using ctx.respond for the first chunk and channel.send for the rest."""
chunks = split_message(text)
await ctx.respond(chunks[0])
for chunk in chunks[1:]:
await ctx.channel.send(chunk)


async def send_long_message(channel, text: str) -> None:
"""Send a potentially long message to a channel, splitting as needed."""
for chunk in split_message(text):
await channel.send(chunk)