7 Commits

Author SHA1 Message Date
e4a748bfb5 update README
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 15s
2026-06-26 19:27:45 -05:00
dbbfbd638f ruff format
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 15s
2026-06-26 19:24:36 -05:00
284e4f92fe Merge pull request 'jonbot fixes' (#14) from jonbot into main
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 7s
Reviewed-on: #14
2026-06-19 06:14:31 +00:00
122239e306 Merge branch 'main' into jonbot 2026-06-19 06:13:43 +00:00
39cc53d501 jonbot fixes 2026-06-19 01:12:46 -05:00
4de0347610 Merge pull request 'jonbot' (#13) from jonbot into main
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 1m17s
Reviewed-on: #13
2026-06-19 06:05:00 +00:00
e94173b3da fix jon chats and merge main into branch 2026-06-18 15:22:20 -05:00
9 changed files with 211 additions and 102 deletions

View File

@@ -22,7 +22,6 @@ jobs:
if [ "$(docker compose ps -q)" ]; then if [ "$(docker compose ps -q)" ]; then
docker compose down docker compose down
fi fi
docker build -t git.crate.zip/crate/garfbot:latest .
docker compose up -d --build docker compose up -d --build
else else
docker restart garfbot docker restart garfbot

View File

@@ -2,7 +2,7 @@ Who is GarfBot?
====== ======
![garfield](https://www.crate.zip/garfield.png) ![garfield](https://www.crate.zip/garfield.png)
GarfBot is a discord bot that uses OpenAI's generative pre-trained models to produce text and images for your personal entertainment and companionship. GarfBot is a discord bot that uses local generative pre-trained AI models (mistral and flux.2-klein) to produce text and images for your personal entertainment and companionship.
<br>There are a few ways you can interact with him on discord, either in a public server or by direct message: <br>There are a few ways you can interact with him on discord, either in a public server or by direct message:
`hey garfield {prompt}` `hey garfield {prompt}`

View File

@@ -7,7 +7,7 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
restart: always restart: always
volumes: volumes:
- ../garfbot:/usr/src/app - /home/crate/garfbot:/usr/src/app
jonbot: jonbot:
image: git.crate.zip/crate/jonbot:latest image: git.crate.zip/crate/jonbot:latest
@@ -17,4 +17,4 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
restart: always restart: always
volumes: volumes:
- ../garfbot/jonbot:/usr/src/app - /home/crate/garfbot/jonbot:/usr/src/app

View File

@@ -13,26 +13,40 @@ intents.messages = True
intents.message_content = True intents.message_content = True
client = discord.Client(intents=intents) client = discord.Client(intents=intents)
@client.event @client.event
async def on_ready(): async def on_ready():
print(f"Logged in as {client.user.name} running {model}.", flush=True) print(f"Logged in as {client.user.name} running {model}.", flush=True)
@client.event @client.event
async def on_message(message): async def on_message(message):
if message.author == client.user: if message.author == client.user:
return return
if message.content.lower().startswith("hey money") or isinstance(message.channel, discord.DMChannel): if message.content.lower().startswith("hey money") or isinstance(
question = message.content[9:] if message.content.lower().startswith("hey money") else message.content message.channel, discord.DMChannel
):
question = (
message.content[9:]
if message.content.lower().startswith("hey money")
else message.content
)
try: try:
response = openai.ChatCompletion.create( response = openai.ChatCompletion.create(
model=model, model=model,
messages=[ messages=[
{"role": "system", "content": "Pretend you are eccentric conspiracy theorist Planetside 2 gamer named Dr. Moneypants."}, {
{"role": "user", "content": f"{question} please keep it short with religious undertones"} "role": "system",
], "content": "Pretend you are eccentric conspiracy theorist Planetside 2 gamer named Dr. Moneypants.",
max_tokens=400 },
{
"role": "user",
"content": f"{question} please keep it short with religious undertones",
},
],
max_tokens=400,
) )
answer = response['choices'][0]['message']['content'] answer = response["choices"][0]["message"]["content"]
answer = answer.replace("an AI language model", "a man of God") answer = answer.replace("an AI language model", "a man of God")
answer = answer.replace("language model AI", "man of God") answer = answer.replace("language model AI", "man of God")
await message.channel.send(answer) await message.channel.send(answer)
@@ -40,14 +54,16 @@ async def on_message(message):
e = str(e) e = str(e)
await message.channel.send(f"`MoneyBot Error: {e}`") await message.channel.send(f"`MoneyBot Error: {e}`")
async def moneybot_connect(): async def moneybot_connect():
while True: while True:
try: try:
await client.start(moneykey) await client.start(moneykey)
except Exception as e: except Exception as e:
e = str(e) e = str(e)
logger.error(f"Moneybot couldn't connect! {e}") logger.error(f"Moneybot couldn't connect! {e}")
await asyncio.sleep(60) await asyncio.sleep(60)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(moneybot_connect()) asyncio.run(moneybot_connect())

View File

@@ -42,18 +42,19 @@ weather = WeatherAPI()
URL_PATTERNS = [ URL_PATTERNS = [
r'https?://(?:www\.)?youtube\.com/watch\?[^\s]*', r"https?://(?:www\.)?youtube\.com/watch\?[^\s]*",
r'https?://youtu\.be/[^\s]*', r"https?://youtu\.be/[^\s]*",
r'https?://(?:open\.)?spotify\.com/[^\s]*', r"https?://(?:open\.)?spotify\.com/[^\s]*",
] ]
def clean_url(url): def clean_url(url):
try: try:
parsed = urlparse(url) parsed = urlparse(url)
if 'youtube.com' in parsed.hostname: if "youtube.com" in parsed.hostname:
params = parse_qs(parsed.query) params = parse_qs(parsed.query)
video_id = params.get('v', [None])[0] video_id = params.get("v", [None])[0]
if not video_id: if not video_id:
return None return None
# timestamp = params.get('t', [None])[0] # timestamp = params.get('t', [None])[0]
@@ -61,10 +62,10 @@ def clean_url(url):
# return f"https://www.youtube.com/watch?v={video_id}&t={timestamp}" # return f"https://www.youtube.com/watch?v={video_id}&t={timestamp}"
return f"https://www.youtube.com/watch?v={video_id}" return f"https://www.youtube.com/watch?v={video_id}"
if 'youtu.be' in parsed.hostname: if "youtu.be" in parsed.hostname:
return f"https://youtu.be{parsed.path}" return f"https://youtu.be{parsed.path}"
if 'spotify.com' in parsed.hostname: if "spotify.com" in parsed.hostname:
return f"https://open.spotify.com{parsed.path}" return f"https://open.spotify.com{parsed.path}"
except Exception: except Exception:

View File

@@ -134,4 +134,3 @@ async def aod_message(garfbot, message):
# if count >= 3 or (len(words) >= 2 and count / len(words) >= 0.75): # if count >= 3 or (len(words) >= 2 and count / len(words) >= 0.75):
# await message.channel.send("Boy, you said it!") # await message.channel.send("Boy, you said it!")

View File

@@ -13,13 +13,31 @@ from garfpy import logger
INVOKEAI_BASE = config.INVOKEAI_URL INVOKEAI_BASE = config.INVOKEAI_URL
_MODEL_KEY = "0eb50094-5c9b-431b-ba01-87e145edb849" _MODEL_KEY = "0eb50094-5c9b-431b-ba01-87e145edb849"
_VAE_KEY = "dde3627c-8a45-4088-93d1-66c44acbb337" _VAE_KEY = "dde3627c-8a45-4088-93d1-66c44acbb337"
_ENCODER_KEY = "7ba22542-4687-4946-a52e-c92f925f4b75" _ENCODER_KEY = "7ba22542-4687-4946-a52e-c92f925f4b75"
_MODEL_REF = {"key": _MODEL_KEY, "hash": "blake3:c3ee838d71d99497db01fae6f304eafd9e734e935f3b783e968d50febb56be2c", "name": "FLUX.2 Klein 4B (GGUF Q4)", "base": "flux2", "type": "main"} _MODEL_REF = {
_VAE_REF = {"key": _VAE_KEY, "hash": "blake3:531855de70db993d0f6181f82cde27d15411d58b7ffa3b2fdce2b9434c0173c2", "name": "FLUX.2 VAE", "base": "flux2", "type": "vae"} "key": _MODEL_KEY,
_ENCODER_REF = {"key": _ENCODER_KEY, "hash": "blake3:af5840e6770dc99f678e69867949c8b9264835915eb82a990e940fa6e4fa6c81", "name": "FLUX.2 Klein Qwen3 4B Encoder", "base": "any", "type": "qwen3_encoder"} "hash": "blake3:c3ee838d71d99497db01fae6f304eafd9e734e935f3b783e968d50febb56be2c",
"name": "FLUX.2 Klein 4B (GGUF Q4)",
"base": "flux2",
"type": "main",
}
_VAE_REF = {
"key": _VAE_KEY,
"hash": "blake3:531855de70db993d0f6181f82cde27d15411d58b7ffa3b2fdce2b9434c0173c2",
"name": "FLUX.2 VAE",
"base": "flux2",
"type": "vae",
}
_ENCODER_REF = {
"key": _ENCODER_KEY,
"hash": "blake3:af5840e6770dc99f678e69867949c8b9264835915eb82a990e940fa6e4fa6c81",
"name": "FLUX.2 Klein Qwen3 4B Encoder",
"base": "any",
"type": "qwen3_encoder",
}
_POLL_INTERVAL = 2 _POLL_INTERVAL = 2
_POLL_ATTEMPTS = 60 _POLL_ATTEMPTS = 60
@@ -31,49 +49,103 @@ def _node_id(prefix: str) -> str:
def _build_graph(prompt: str) -> dict: def _build_graph(prompt: str) -> dict:
seed = int(time.time() * 1000) % (2 ** 31) seed = int(time.time() * 1000) % (2**31)
p = _node_id("positive_prompt") p = _node_id("positive_prompt")
ml = _node_id("flux2_klein_model_loader") ml = _node_id("flux2_klein_model_loader")
te = _node_id("flux2_klein_text_encoder") te = _node_id("flux2_klein_text_encoder")
dn = _node_id("flux2_denoise") dn = _node_id("flux2_denoise")
out = _node_id("canvas_output") out = _node_id("canvas_output")
nodes = { nodes = {
p: {"id": p, "is_intermediate": True, "use_cache": True, "value": prompt, "type": "string"}, p: {
ml: {"id": ml, "is_intermediate": True, "use_cache": True, "type": "flux2_klein_model_loader", "id": p,
"model": _MODEL_REF, "vae_model": _VAE_REF, "qwen3_encoder_model": _ENCODER_REF}, "is_intermediate": True,
te: {"id": te, "is_intermediate": True, "use_cache": True, "type": "flux2_klein_text_encoder"}, "use_cache": True,
dn: {"id": dn, "is_intermediate": True, "use_cache": True, "type": "flux2_denoise", "seed": seed}, "value": prompt,
out: {"id": out, "is_intermediate": False, "use_cache": False, "type": "flux2_vae_decode"}, "type": "string",
},
ml: {
"id": ml,
"is_intermediate": True,
"use_cache": True,
"type": "flux2_klein_model_loader",
"model": _MODEL_REF,
"vae_model": _VAE_REF,
"qwen3_encoder_model": _ENCODER_REF,
},
te: {
"id": te,
"is_intermediate": True,
"use_cache": True,
"type": "flux2_klein_text_encoder",
},
dn: {
"id": dn,
"is_intermediate": True,
"use_cache": True,
"type": "flux2_denoise",
"seed": seed,
},
out: {
"id": out,
"is_intermediate": False,
"use_cache": False,
"type": "flux2_vae_decode",
},
} }
edges = [ edges = [
{"source": {"node_id": ml, "field": "qwen3_encoder"}, "destination": {"node_id": te, "field": "qwen3_encoder"}}, {
{"source": {"node_id": ml, "field": "max_seq_len"}, "destination": {"node_id": te, "field": "max_seq_len"}}, "source": {"node_id": ml, "field": "qwen3_encoder"},
{"source": {"node_id": p, "field": "value"}, "destination": {"node_id": te, "field": "prompt"}}, "destination": {"node_id": te, "field": "qwen3_encoder"},
{"source": {"node_id": ml, "field": "transformer"}, "destination": {"node_id": dn, "field": "transformer"}}, },
{"source": {"node_id": ml, "field": "vae"}, "destination": {"node_id": dn, "field": "vae"}}, {
{"source": {"node_id": te, "field": "conditioning"}, "destination": {"node_id": dn, "field": "positive_text_conditioning"}}, "source": {"node_id": ml, "field": "max_seq_len"},
{"source": {"node_id": ml, "field": "vae"}, "destination": {"node_id": out, "field": "vae"}}, "destination": {"node_id": te, "field": "max_seq_len"},
{"source": {"node_id": dn, "field": "latents"}, "destination": {"node_id": out, "field": "latents"}}, },
{
"source": {"node_id": p, "field": "value"},
"destination": {"node_id": te, "field": "prompt"},
},
{
"source": {"node_id": ml, "field": "transformer"},
"destination": {"node_id": dn, "field": "transformer"},
},
{
"source": {"node_id": ml, "field": "vae"},
"destination": {"node_id": dn, "field": "vae"},
},
{
"source": {"node_id": te, "field": "conditioning"},
"destination": {"node_id": dn, "field": "positive_text_conditioning"},
},
{
"source": {"node_id": ml, "field": "vae"},
"destination": {"node_id": out, "field": "vae"},
},
{
"source": {"node_id": dn, "field": "latents"},
"destination": {"node_id": out, "field": "latents"},
},
] ]
return {"nodes": nodes, "edges": edges} return {"nodes": nodes, "edges": edges}
async def _poll_batch(session: aiohttp.ClientSession, base: str, batch_id: str) -> bool: async def _poll_batch(session: aiohttp.ClientSession, base: str, batch_id: str) -> bool:
"""Poll batch status until completed, failed, or timed out. Returns True on success."""
for _ in range(_POLL_ATTEMPTS): for _ in range(_POLL_ATTEMPTS):
await asyncio.sleep(_POLL_INTERVAL) await asyncio.sleep(_POLL_INTERVAL)
try: try:
async with session.get(f"{base}/api/v1/queue/default/b/{batch_id}/status") as resp: async with session.get(
f"{base}/api/v1/queue/default/b/{batch_id}/status"
) as resp:
if not resp.ok: if not resp.ok:
continue continue
s = await resp.json(content_type=None) s = await resp.json(content_type=None)
total = s.get("total", 0) total = s.get("total", 0)
completed = s.get("completed", 0) completed = s.get("completed", 0)
failed = s.get("failed", 0) failed = s.get("failed", 0)
if total > 0 and failed >= total: if total > 0 and failed >= total:
logger.error(f"Batch {batch_id} failed") logger.error(f"Batch {batch_id} failed")
return False return False
@@ -84,7 +156,9 @@ async def _poll_batch(session: aiohttp.ClientSession, base: str, batch_id: str)
return False return False
async def _get_image_name(session: aiohttp.ClientSession, base: str, batch_id: str) -> str | None: async def _get_image_name(
session: aiohttp.ClientSession, base: str, batch_id: str
) -> str | None:
try: try:
async with session.get(f"{base}/api/v1/queue/default/i/{batch_id}") as resp: async with session.get(f"{base}/api/v1/queue/default/i/{batch_id}") as resp:
if resp.ok: if resp.ok:
@@ -110,8 +184,9 @@ async def _get_image_name(session: aiohttp.ClientSession, base: str, batch_id: s
return None return None
async def _fetch_image_bytes(session: aiohttp.ClientSession, base: str, name: str) -> bytes | None: async def _fetch_image_bytes(
"""Try the full image endpoint, then fall back to thumbnail.""" session: aiohttp.ClientSession, base: str, name: str
) -> bytes | None:
urls = [ urls = [
f"{base}/api/v1/images/i/{name}/full", f"{base}/api/v1/images/i/{name}/full",
f"{base}/api/v1/images/i/{name}/thumbnail", f"{base}/api/v1/images/i/{name}/thumbnail",
@@ -119,9 +194,11 @@ async def _fetch_image_bytes(session: aiohttp.ClientSession, base: str, name: st
for url in urls: for url in urls:
try: try:
async with session.get(url) as resp: async with session.get(url) as resp:
ct = resp.headers.get("Content-Type", "") ct = resp.headers.get("Content-Type", "")
data = await resp.read() data = await resp.read()
logger.info(f"Image fetch {url}: status={resp.status} content-type={ct} size={len(data)}") logger.info(
f"Image fetch {url}: status={resp.status} content-type={ct} size={len(data)}"
)
if "html" not in ct and len(data) >= _MIN_IMAGE_BYTES: if "html" not in ct and len(data) >= _MIN_IMAGE_BYTES:
return data return data
except Exception as e: except Exception as e:
@@ -131,10 +208,10 @@ async def _fetch_image_bytes(session: aiohttp.ClientSession, base: str, name: st
class GarfAI: class GarfAI:
def __init__(self): def __init__(self):
self.baseurl = config.BASE_URL self.baseurl = config.BASE_URL
self.sysprompt = config.SYSTEM_PROMPT self.sysprompt = config.SYSTEM_PROMPT
self.txtmodel = config.TXT_MODEL self.txtmodel = config.TXT_MODEL
self.imgmodel = config.IMG_MODEL self.imgmodel = config.IMG_MODEL
self._oai = AsyncOpenAI( self._oai = AsyncOpenAI(
api_key=config.OPENAI_TOKEN, api_key=config.OPENAI_TOKEN,
@@ -145,7 +222,9 @@ class GarfAI:
async def garfpic(self, ctx, prompt): async def garfpic(self, ctx, prompt):
await self.image_request_queue.put({"ctx": ctx, "prompt": prompt}) await self.image_request_queue.put({"ctx": ctx, "prompt": prompt})
async def generate_image(self, session: aiohttp.ClientSession, prompt: str) -> bytes | str: async def generate_image(
self, session: aiohttp.ClientSession, prompt: str
) -> bytes | str:
base = INVOKEAI_BASE base = INVOKEAI_BASE
try: try:
@@ -157,7 +236,7 @@ class GarfAI:
text = await resp.text() text = await resp.text()
logger.error(f"InvokeAI enqueue failed {resp.status}: {text}") logger.error(f"InvokeAI enqueue failed {resp.status}: {text}")
return "`GarfBot Error: InvokeAI rejected the request`" return "`GarfBot Error: InvokeAI rejected the request`"
data = await resp.json(content_type=None) data = await resp.json(content_type=None)
batch_id = data["batch"]["batch_id"] batch_id = data["batch"]["batch_id"]
except Exception as e: except Exception as e:
logger.error(f"InvokeAI enqueue error: {e}") logger.error(f"InvokeAI enqueue error: {e}")
@@ -182,19 +261,21 @@ class GarfAI:
return "`GarfBot Error: Odie`" return "`GarfBot Error: Odie`"
async def process_image_requests(self): async def process_image_requests(self):
async with aiohttp.ClientSession(headers={"Accept": "application/json"}) as session: async with aiohttp.ClientSession(
headers={"Accept": "application/json"}
) as session:
while True: while True:
request = await self.image_request_queue.get() request = await self.image_request_queue.get()
ctx = request["ctx"] ctx = request["ctx"]
prompt = request["prompt"] prompt = request["prompt"]
result = await self.generate_image(session, prompt) result = await self.generate_image(session, prompt)
if isinstance(result, bytes): if isinstance(result, bytes):
logger.info("Sending image...") logger.info("Sending image...")
image = io.BytesIO(result) image = io.BytesIO(result)
timestamp = ctx.message.created_at.strftime("%Y%m%d%H%M%S") timestamp = ctx.message.created_at.strftime("%Y%m%d%H%M%S")
filename = f"{timestamp}_generated_image.png" filename = f"{timestamp}_generated_image.png"
try: try:
await ctx.reply(file=discord.File(fp=image, filename=filename)) await ctx.reply(file=discord.File(fp=image, filename=filename))
except Exception as e: except Exception as e:
@@ -211,7 +292,7 @@ class GarfAI:
model=self.txtmodel, model=self.txtmodel,
messages=[ messages=[
{"role": "system", "content": self.sysprompt}, {"role": "system", "content": self.sysprompt},
{"role": "user", "content": question}, {"role": "user", "content": question},
], ],
max_tokens=400, max_tokens=400,
temperature=1.2, temperature=1.2,
@@ -231,7 +312,9 @@ class GarfAI:
async def wikisum(self, query: str) -> str: async def wikisum(self, query: str) -> str:
try: try:
summary = wikipedia.summary(query) summary = wikipedia.summary(query)
return await self.generate_chat(f"Please summarize in your own words: {summary}") return await self.generate_chat(
f"Please summarize in your own words: {summary}"
)
except wikipedia.exceptions.DisambiguationError as e: except wikipedia.exceptions.DisambiguationError as e:
options = ", ".join(e.options[:3]) options = ", ".join(e.options[:3])
return f"`GarfBot Error: Ambiguous query — did you mean: {options}?`" return f"`GarfBot Error: Ambiguous query — did you mean: {options}?`"

View File

@@ -47,7 +47,7 @@ async def generate_qr(text):
qr = qrcode.QRCode( qr = qrcode.QRCode(
version=version, version=version,
error_correction=qrcode.constants.ERROR_CORRECT_L, # type: ignore error_correction=qrcode.constants.ERROR_CORRECT_L, # type: ignore
box_size=box_size, box_size=box_size,
border=4, border=4,
) )
@@ -58,7 +58,7 @@ async def generate_qr(text):
qr_image = qr.make_image(fill_color="black", back_color="white") qr_image = qr.make_image(fill_color="black", back_color="white")
img_buffer = BytesIO() img_buffer = BytesIO()
qr_image.save(img_buffer, format="PNG") # type: ignore qr_image.save(img_buffer, format="PNG") # type: ignore
img_buffer.seek(0) img_buffer.seek(0)
return img_buffer return img_buffer

View File

@@ -1,10 +1,11 @@
import config import config
import openai import openai
from openai import AsyncOpenAI
import discord import discord
from discord.ext import commands
import asyncio import asyncio
import logging import logging
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from openai import AsyncOpenAI
logger = logging.getLogger("garflog") logger = logging.getLogger("garflog")
@@ -32,59 +33,69 @@ if not logger.hasHandlers():
openai.api_key = config.OPENAI_TOKEN openai.api_key = config.OPENAI_TOKEN
jonkey = config.JONBOT_TOKEN jonkey = config.JONBOT_TOKEN
model = config.TXT_MODEL txtmodel = config.TXT_MODEL
sysprompt = config.SYSTEM_PROMPT
intents = discord.Intents.default() intents = discord.Intents.default()
intents.messages = True intents.messages = True
intents.message_content = True intents.message_content = True
client = discord.Client(intents=intents)
client = commands.Bot(
command_prefix=["Jonbot ", "jonbot ", "Jon", "jon"],
case_insensitive=True,
intents=intents,
)
@client.event @client.event
async def on_ready(): async def on_ready():
print(f"Logged in as {client.user.name} running {model}.", flush=True) print(f"Logged in as {client.user.name} running {txtmodel}.", flush=True)
@client.command(name="chat")
async def jonchat(ctx, *, prompt):
if "is this true" in prompt.lower():
messages = [msg async for msg in ctx.channel.history(limit=2)]
prompt = messages[1].content
prompt = f"Is this true: {prompt}"
answer = await generate_chat(prompt)
logger.info(
f"Chat Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
)
await ctx.reply(answer)
@client.event @client.event
async def on_message(message): async def on_message(message):
if message.author == client.user: if message.author == client.user:
return return
if message.content.lower().startswith("hey jon") or isinstance(message.channel, discord.DMChannel):
question = message.content[7:] if message.content.lower().startswith("hey jon") else message.content content = message.content.strip()
try: lower = content.lower()
response = openai.ChatCompletion.create( if lower.startswith("hey jon") or isinstance(message.channel, discord.DMChannel):
model=model, ctx = await client.get_context(message)
messages=[ await jonchat(ctx, prompt=content)
{"role": "system", "content": "Pretend you are friendly Jon Arbuckle."},
{"role": "user", "content": f"{question}"}
],
max_tokens=400
)
answer = response['choices'][0]['message']['content']
answer = answer.replace("AI language model", "American citizen")
answer = answer.replace("language model AI", "citizen of the United States")
await message.channel.send(answer)
except Exception as e:
e = str(e)
await message.channel.send(f"`JonBot Error: {e}`")
oai = AsyncOpenAI( oai = AsyncOpenAI(
api_key=config.OPENAI_TOKEN, api_key=config.OPENAI_TOKEN,
base_url=config.BASE_URL, base_url=config.BASE_URL,
) )
async def generate_chat(self, question: str) -> str:
async def generate_chat(question: str) -> str:
try: try:
response = await oai.chat.completions.create( response = await oai.chat.completions.create(
model=self.txtmodel, model=txtmodel,
messages=[ messages=[
{"role": "system", "content": self.sysprompt}, {"role": "system", "content": sysprompt},
{"role": "user", "content": question}, {"role": "user", "content": question},
], ],
max_tokens=400, max_tokens=400,
temperature=1.2, temperature=1.2,
) )
answer = response.choices[0].message.content answer = response.choices[0].message.content
return answer.replace("an AI language model", "a cartoon animal") return answer
except openai.BadRequestError as e: except openai.BadRequestError as e:
logger.error(e) logger.error(e)
return f"`JonBot Error: {e}`" return f"`JonBot Error: {e}`"
@@ -96,15 +107,15 @@ async def generate_chat(self, question: str) -> str:
return "`JonBot Error: Liz`" return "`JonBot Error: Liz`"
async def jonbot_connect(): async def jonbot_connect():
while True: while True:
try: try:
await client.start(jonkey) await client.start(jonkey)
except Exception as e: except Exception as e:
e = str(e) e = str(e)
logger.error(f"Jonbot couldn't connect! {e}") logger.error(f"Jonbot couldn't connect! {e}")
await asyncio.sleep(60) await asyncio.sleep(60)
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(jonbot_connect()) asyncio.run(jonbot_connect())