ai memory increased
This commit is contained in:
1 parent
6c77f14e42
commit
8b336ccced
6 files changed
+119
-33
No files matched your search
+76
-3
@@ -6,13 +6,14 @@ import re
|
|||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
|
import logging
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
# pyrefly: ignore [missing-import]
|
# pyrefly: ignore [missing-import]
|
||||||
from openai import AsyncOpenAI
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
#only last 12 messages on discord will be fed into the ai's memory
|
#only last 12 messages on discord will be fed into the ai's memory
|
||||||
MEMORY_SIZE = 12
|
MEMORY_SIZE = 15
|
||||||
|
|
||||||
# These are the ONLY two valid DeepSeek V4 model IDs. deepseek-chat and
|
# These are the ONLY two valid DeepSeek V4 model IDs. deepseek-chat and
|
||||||
# deepseek-reasoner are legacy aliases scheduled for retirement on
|
# deepseek-reasoner are legacy aliases scheduled for retirement on
|
||||||
@@ -170,11 +171,51 @@ class AIRoleplay(commands.Cog):
|
|||||||
self.thinking_channels = set() # channel_ids with thinking ON
|
self.thinking_channels = set() # channel_ids with thinking ON
|
||||||
self.thinking_effort = {} # channel_id -> 'high' | 'max'
|
self.thinking_effort = {} # channel_id -> 'high' | 'max'
|
||||||
self.temperature = 0.85
|
self.temperature = 0.85
|
||||||
|
self.channel_summary = {} # channel_id -> str
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# CORE AI LOGIC & LISTENERS
|
# CORE AI LOGIC & LISTENERS
|
||||||
# ==========================================
|
# ==========================================
|
||||||
|
|
||||||
|
#this helper summarizes evicted memory to maintain long-term context
|
||||||
|
async def update_summary(self, channel_id, evicted_message):
|
||||||
|
try:
|
||||||
|
current_summary = self.channel_summary.get(channel_id, "")
|
||||||
|
evicted_text = evicted_message.get('content', '')
|
||||||
|
|
||||||
|
# handle vision support list format
|
||||||
|
if isinstance(evicted_text, list):
|
||||||
|
evicted_text = next((item['text'] for item in evicted_text if item.get('type') == 'text'), '')
|
||||||
|
|
||||||
|
if not evicted_text:
|
||||||
|
return
|
||||||
|
|
||||||
|
sys_prompt = (
|
||||||
|
"You are a background memory summarizer. "
|
||||||
|
"Merge the new evicted message into the existing summary. "
|
||||||
|
"Keep the total summary under 150 words. "
|
||||||
|
"Only retain facts relevant to story/relationship continuity, ignore minor chitchat."
|
||||||
|
)
|
||||||
|
user_prompt = f"Existing Summary:\n{current_summary}\n\nEvicted Message:\n{evicted_text}"
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
"model": MODEL_FLASH,
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": sys_prompt},
|
||||||
|
{"role": "user", "content": user_prompt}
|
||||||
|
],
|
||||||
|
"max_tokens": 300,
|
||||||
|
"extra_body": {"thinking": {"type": "disabled"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = await self.client.chat.completions.create(**kwargs)
|
||||||
|
new_summary = (resp.choices[0].message.content or '').strip()
|
||||||
|
if new_summary:
|
||||||
|
self.channel_summary[channel_id] = new_summary
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Summarization failed for channel {channel_id}: {e}")
|
||||||
|
|
||||||
#only accepts me and hizuki
|
#only accepts me and hizuki
|
||||||
def is_allowed(self, user):
|
def is_allowed(self, user):
|
||||||
return user.id in self.allowed_users
|
return user.id in self.allowed_users
|
||||||
@@ -296,9 +337,21 @@ class AIRoleplay(commands.Cog):
|
|||||||
|
|
||||||
# DeepSeek and older models prefer pure strings if there are no images
|
# DeepSeek and older models prefer pure strings if there are no images
|
||||||
final_content = user_content if has_images else user_content[0]["text"]
|
final_content = user_content if has_images else user_content[0]["text"]
|
||||||
|
|
||||||
|
# summarize evicted memory before it drops off the deque
|
||||||
|
if len(mem) >= mem.maxlen:
|
||||||
|
await self.update_summary(cid, mem[0])
|
||||||
|
|
||||||
mem.append({'role': 'user', 'content': final_content})
|
mem.append({'role': 'user', 'content': final_content})
|
||||||
|
|
||||||
api_msgs = [{'role': 'system', 'content': prompt}] + list(mem)
|
api_msgs = [{'role': 'system', 'content': prompt}]
|
||||||
|
|
||||||
|
# inject the background summary into the system prompt if it exists
|
||||||
|
summary = self.channel_summary.get(cid)
|
||||||
|
if summary:
|
||||||
|
api_msgs[0]['content'] += f"\n\nMEMORY (earlier context, may be approximate):\n{summary}"
|
||||||
|
|
||||||
|
api_msgs += list(mem)
|
||||||
model_to_use = self.active_model # always MODEL_FLASH or MODEL_PRO, never swapped
|
model_to_use = self.active_model # always MODEL_FLASH or MODEL_PRO, never swapped
|
||||||
|
|
||||||
#VISION SUPPORT END
|
#VISION SUPPORT END
|
||||||
@@ -478,6 +531,26 @@ class AIRoleplay(commands.Cog):
|
|||||||
embed.set_footer(text=f'model: {self.active_model} · this channel only')
|
embed.set_footer(text=f'model: {self.active_model} · this channel only')
|
||||||
await ctx.reply(embed=embed, view=ThinkingView(self, cid))
|
await ctx.reply(embed=embed, view=ThinkingView(self, cid))
|
||||||
|
|
||||||
|
#this shows the current memory summary for the channel
|
||||||
|
@commands.command()
|
||||||
|
async def summary(self, ctx):
|
||||||
|
if not self.is_allowed(ctx.author):
|
||||||
|
return
|
||||||
|
summary_text = self.channel_summary.get(ctx.channel.id)
|
||||||
|
if not summary_text:
|
||||||
|
return await ctx.reply("No summary yet.")
|
||||||
|
|
||||||
|
embed = discord.Embed(title='🧠 Channel Memory Summary', description=summary_text, color=0x2b2d31)
|
||||||
|
await ctx.reply(embed=embed)
|
||||||
|
|
||||||
|
#this wipes the memory summary for the channel
|
||||||
|
@commands.command()
|
||||||
|
async def clearsummary(self, ctx):
|
||||||
|
if not self.is_allowed(ctx.author):
|
||||||
|
return
|
||||||
|
self.channel_summary.pop(ctx.channel.id, None)
|
||||||
|
await ctx.reply("Channel summary cleared.")
|
||||||
|
|
||||||
#this checks thinking status without opening the menu, only usable by me and hizu
|
#this checks thinking status without opening the menu, only usable by me and hizu
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def thinkstatus(self, ctx):
|
async def thinkstatus(self, ctx):
|
||||||
@@ -499,7 +572,7 @@ class AIRoleplay(commands.Cog):
|
|||||||
return await ctx.reply("temperature must be between 0.0 and 2.0.")
|
return await ctx.reply("temperature must be between 0.0 and 2.0.")
|
||||||
|
|
||||||
self.temperature = value
|
self.temperature = value
|
||||||
await ctx.reply(f"🌡️ **Temperature set to {value}**\n*(Lower = stricter/robotic, Higher = creative/chaotic)*")
|
await ctx.reply(f"**Temperature set to {value}**\n*(Lower = stricter/robotic, Higher = creative/chaotic)*")
|
||||||
|
|
||||||
async def setup(bot):
|
async def setup(bot):
|
||||||
await bot.add_cog(AIRoleplay(bot))
|
await bot.add_cog(AIRoleplay(bot))
|
||||||
+1
-30
@@ -15,36 +15,7 @@ class General(commands.Cog):
|
|||||||
async def yuuka(self, ctx):
|
async def yuuka(self, ctx):
|
||||||
await ctx.send('kanpeki!')
|
await ctx.send('kanpeki!')
|
||||||
|
|
||||||
#will soon be removed from here when im not lazy
|
|
||||||
@commands.command()
|
|
||||||
async def leaks(self, ctx):
|
|
||||||
rss_url = os.getenv('TWITTER_RSS_URL')
|
|
||||||
if not rss_url:
|
|
||||||
return await ctx.reply('rss url not configured.')
|
|
||||||
|
|
||||||
try:
|
|
||||||
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
|
|
||||||
async with aiohttp.ClientSession(headers=headers) as session:
|
|
||||||
async with session.get(rss_url) as resp:
|
|
||||||
text = await resp.text()
|
|
||||||
|
|
||||||
feed = feedparser.parse(text)
|
|
||||||
if not feed.entries:
|
|
||||||
return await ctx.reply('no tweets found.')
|
|
||||||
|
|
||||||
response = "**Latest 5 Leaks:**\n"
|
|
||||||
for entry in feed.entries[:5]:
|
|
||||||
title = entry.get('title', '')
|
|
||||||
link = entry.get('link', '')
|
|
||||||
|
|
||||||
if 'nitter.net' in link:
|
|
||||||
link = link.replace('nitter.net', 'twitter.com').replace('#m', '')
|
|
||||||
|
|
||||||
response += f"\n{title}\n{link}\n"
|
|
||||||
|
|
||||||
await ctx.reply(response[:2000])
|
|
||||||
except Exception as e:
|
|
||||||
await ctx.reply(f'failed to fetch leaks: {e}')
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def weather(self, ctx, *, city: str = "kayseri"):
|
async def weather(self, ctx, *, city: str = "kayseri"):
|
||||||
|
|||||||
Whitespace-only changes.
Binary file not shown.
@@ -56,6 +56,7 @@ async def main():
|
|||||||
await bot.load_extension('cogs.ffmpeg_things')
|
await bot.load_extension('cogs.ffmpeg_things')
|
||||||
await bot.load_extension('cogs.currency_converter')
|
await bot.load_extension('cogs.currency_converter')
|
||||||
await bot.load_extension('ai.ai_roleplay')
|
await bot.load_extension('ai.ai_roleplay')
|
||||||
|
await bot.load_extension('twitterbridge.leaks_cmd')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await bot.start(os.getenv('DISCORD_TOKEN'))
|
await bot.start(os.getenv('DISCORD_TOKEN'))
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import os
|
||||||
|
import aiohttp
|
||||||
|
import feedparser
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
class TwitterLeaks(commands.Cog):
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command()
|
||||||
|
async def leaks(self, ctx):
|
||||||
|
rss_url = os.getenv('TWITTER_RSS_URL')
|
||||||
|
if not rss_url:
|
||||||
|
return await ctx.reply('rss url not configured.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
|
||||||
|
async with aiohttp.ClientSession(headers=headers) as session:
|
||||||
|
async with session.get(rss_url) as resp:
|
||||||
|
text = await resp.text()
|
||||||
|
|
||||||
|
feed = feedparser.parse(text)
|
||||||
|
if not feed.entries:
|
||||||
|
return await ctx.reply('no tweets found.')
|
||||||
|
|
||||||
|
response = "**Latest 5 Leaks:**\n"
|
||||||
|
for entry in feed.entries[:5]:
|
||||||
|
title = entry.get('title', '')
|
||||||
|
link = entry.get('link', '')
|
||||||
|
|
||||||
|
if 'nitter.net' in link:
|
||||||
|
link = link.replace('nitter.net', 'twitter.com').replace('#m', '')
|
||||||
|
|
||||||
|
response += f"\n{title}\n{link}\n"
|
||||||
|
|
||||||
|
await ctx.reply(response[:2000])
|
||||||
|
except Exception as e:
|
||||||
|
await ctx.reply(f'failed to fetch leaks: {e}')
|
||||||
|
|
||||||
|
async def setup(bot):
|
||||||
|
await bot.add_cog(TwitterLeaks(bot))
|
||||||
Reference in new issue
Block a user