Files
vibe-bot/vibe_bot/services/conversation_service.py
T
2026-08-19 13:16:43 -04:00

148 lines
5.1 KiB
Python

"""Conversation service: run a capped bot-vs-bot conversation (talkforme)."""
from __future__ import annotations
import asyncio
import logging
from typing import TYPE_CHECKING
from vibe_bot import llm_client
from vibe_bot.config import CHAT_MODEL, MAX_COMPLETION_TOKENS
from vibe_bot.prompts import RESPONSE_LENGTH_HINT
from vibe_bot.textutil import split_message
if TYPE_CHECKING:
from discord.ext.commands import Bot, Context
from vibe_bot.database import CustomBotManager
logger = logging.getLogger(__name__)
# Input size bound: reject oversized topics before they reach the LLM.
MAX_TOPIC_LENGTH = 500
# Hard cap on the number of replies a single !talkforme invocation produces.
TALK_LIMIT = 20
def flip_counter(counter: int) -> int:
"""Flip between 0 and 1 (the two conversing bots)."""
return 1 if counter == 0 else 0
class ConversationService:
"""Runs a two-bot conversation about a topic, chunking each reply."""
def __init__(self, manager: CustomBotManager) -> None:
self._manager = manager
async def run(
self,
ctx: Context[Bot],
bot1: str,
bot2: str,
limit: str,
topic: str,
) -> None:
"""Have ``bot1`` and ``bot2`` talk about ``topic`` for up to ``limit`` replies.
Args:
ctx: The Discord command context.
bot1: Name of the first custom bot.
bot2: Name of the second custom bot.
limit: Requested number of replies (string; parsed to an int).
topic: The conversation topic.
"""
if len(topic) > MAX_TOPIC_LENGTH:
logger.warning(
"Talkforme topic too long from user %s: length=%d",
ctx.author.id,
len(topic),
)
await ctx.send(f"Topic too long. Max {MAX_TOPIC_LENGTH} characters.")
return
bot1_info = await asyncio.to_thread(self._manager.get_custom_bot, bot1)
if not bot1_info:
await ctx.send(f"{bot1} is not a real bot...")
return
bot1_prompt = bot1_info[1]
bot2_info = await asyncio.to_thread(self._manager.get_custom_bot, bot2)
if not bot2_info:
await ctx.send(f"{bot2} is not a real bot...")
return
bot2_prompt = bot2_info[1]
try:
message_limit = int(limit)
except ValueError:
await ctx.send("Message limit must be an integer.")
return
effective_limit = min(message_limit, TALK_LIMIT)
await ctx.send(
f"{bot1} is going to talk to {bot2} "
f'about "{topic[:50]}" for {effective_limit} replies.',
)
bot_list = [(bot1, bot1_prompt), (bot2, bot2_prompt)]
async def send_chunked(text: str) -> None:
"""Send text in 1000-char chunks to stay under Discord's limit."""
for chunk in split_message(text, 1000):
await ctx.send(chunk)
message_counter = 0
bot_counter = 0
current_bot = bot_list[bot_counter]
prompt_histories: list[list[dict[str, str]]] = [
[{"role": "user", "content": topic}],
[{"role": "assistant", "content": topic}],
]
first_bot_response = await llm_client.chat_completion_with_history(
system_prompt=(
current_bot[1] + f"\n{RESPONSE_LENGTH_HINT} "
f"You are talking to {current_bot[flip_counter(bot_counter)][0]}"
),
prompts=prompt_histories[bot_counter],
model=CHAT_MODEL,
max_tokens=MAX_COMPLETION_TOKENS,
)
await ctx.send(f"## {current_bot[0]}")
await send_chunked(first_bot_response)
prompt_histories[0].append({"role": "assistant", "content": first_bot_response})
prompt_histories[1].append({"role": "user", "content": first_bot_response})
bot_counter = flip_counter(counter=bot_counter)
while message_counter < effective_limit:
current_bot = bot_list[bot_counter]
logger.debug("Current bot is %s", current_bot[0])
bot_response = await llm_client.chat_completion_with_history(
system_prompt=(
current_bot[1] + f"\n{RESPONSE_LENGTH_HINT} "
f"You are talking to {current_bot[flip_counter(bot_counter)][0]}"
),
prompts=prompt_histories[bot_counter],
model=CHAT_MODEL,
max_tokens=MAX_COMPLETION_TOKENS,
)
message_counter += 1
prompt_histories[bot_counter].append(
{"role": "assistant", "content": bot_response},
)
prompt_histories[flip_counter(bot_counter)].append(
{"role": "user", "content": bot_response},
)
await ctx.send(f"## {current_bot[0]}")
await send_chunked(bot_response)
bot_counter = flip_counter(counter=bot_counter)
logger.debug(
"Message counter is %d/%d",
message_counter,
effective_limit,
)