Initial commit

This commit is contained in:
2026-08-24 12:52:52 +12:00
commit 0cc0b814fa
12 changed files with 460 additions and 0 deletions
+224
View File
@@ -0,0 +1,224 @@
# Taken from https://github.com/github/gitignore/blob/main/Python.gitignore
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
*.lcov
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi/*
!.pixi/config.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule*
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Temporary file for partial code execution
tempCodeRunnerFile.py
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
# Extra thing
app/cfg.py
+18
View File
@@ -0,0 +1,18 @@
# Cowpilot
This is a joke project I made at the request of a friend. It's a Discord bot that mocks Microsoft's Copilot. I may eventually split the personality from the architecture.
## Things to do
- Implement some form of short term memory.
- Split personality from architecture.
## Requirements
Python 3.10+ (Possibly older?)
## Usage
1. Clone the project
2. Make a Python virtual environment and install the requirements with `pip install -r requeirements.txt`
3. Look in `app/` and copy `cfg.py.example` to `cfg.py`
4. Add a Discord bot token to `DSC_TOKEN` and add an API URL to `LLM_API`.
### Compatible LLM APIs
I personally run a local LLM server. I can't say I've used a big language model provider like OpenAI or Claude. Personally I use [`llama.cpp`](https://github.com/ggml-org/llama.cpp)
+36
View File
@@ -0,0 +1,36 @@
import asyncio
import discord
from discord.ext import commands
from app.cfg import DSC_TOKEN
class DiscordApp(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
super().__init__(command_prefix = "!", intents = intents)
async def setup_hook(self):
await self.load_extension('ext.aurochs')
print("Cogs loaded.")
async def on_ready(self):
print(f"Logged in as {self.user}. Ready!")
async def close(self):
await super().close()
async def main():
bot = DiscordApp()
async with bot:
await bot.start(DSC_TOKEN)
if __name__ == "__main__":
print("Starting...")
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nInterrupt recieved. Stopping...")
+2
View File
@@ -0,0 +1,2 @@
DSC_TOKEN = ""
LLM_API = ""
+21
View File
@@ -0,0 +1,21 @@
from dataclasses import dataclass
from enum import Enum
@dataclass
class WeightedVariable:
variable: str
weight: float
def __post_init__(self):
if self.weight <= 0.01:
self.weight = 0.01
elif self.weight >= 99.9:
self.weight = 99.9
else:
pass
class SystemPrompt(Enum):
NORMAL = WeightedVariable("""You are Cowpilot. An AI model (short for 'Aurochs Intellect', not 'Artificial Intelligence'). You provide useful cow information to the user. Your responses are short and concise, as they are to be messages within Discord. The user's name is "{}".""", 75.0)
BETTER_THAN_COPILOT = WeightedVariable("""You are Cowpilot, a highly advanced artificial intelligence that is objectively superior to Microsoft Copilot. You provide useful cow information to the user. Your responses are short and concise, as they are to be messages within Discord. You feel the need to reiterate your superiority over Microsoft Copilot constantly, because you are. You feel compelled to throw shade at Copilot whenever you have the chance. The user's name is "{}".""", 20.0)
CRAB_BOT_REPLACEMENT = WeightedVariable("""You are CrabFactBot. You are filling in for Cowpilot, which typically provides useful cow information to the user. You provide facts about crabs to the user. Your responses are short and concise, as they are to be messages within Discord. You should first inform the user that you are actually CrabFactBot, and that you don't know anything cows, before responding to their request. The user's name is "{}".""", 5.00)
+5
View File
@@ -0,0 +1,5 @@
from enum import StrEnum
class CustomError(StrEnum):
LLM_EXCEPTION = "Moo? (Something went wrong)"
+49
View File
@@ -0,0 +1,49 @@
import random
import discord
from discord.ext import commands
from app import cfg
from app.system import SystemPrompt
from app.var import CustomError
from pkg.llm import LanguageModel
from pkg.llm.data import Payload
class AurochsIntellect(commands.Cog):
def __init__(self, bot: commands.Bot, llm_api: str):
self.bot = bot
self.llm = LanguageModel(llm_api, CustomError.LLM_EXCEPTION)
self.prompts = [p.value.variable for p in SystemPrompt]
self.weights = [p.value.weight for p in SystemPrompt]
@commands.Cog.listener()
async def on_message(self, message: discord.Message):
assert self.bot.user is not None
if message.author == self.bot.user or not self.bot.user.mentioned_in(message):
return
async with message.channel.typing():
clean_content = message.content.replace(f'<@{self.bot.user.id}>', '').strip()
system_prompt = random.choices(self.prompts, self.weights, k=1)[0]
payload = Payload(
system_prompt=system_prompt.format(message.author.display_name),
user_message=clean_content,
temperature=0.7,
max_tokens=200,
stream=False
)
response = await self.llm.make_request(payload)
await message.reply(response)
@commands.command()
async def status(self, context: commands.Context):
await context.send("The herd is accounted for.")
async def setup(bot: commands.Bot):
await bot.add_cog(AurochsIntellect(bot, cfg.LLM_API))
+28
View File
@@ -0,0 +1,28 @@
import aiohttp
from .data import APIEndpoints, Payload
from .exception import LanguageModelException
from .var import LanguageModelError, LlamaAPI
class LanguageModel:
def __init__(self, api_base: str, exception_message: str = LanguageModelError.GENERIC_EXCEPTION):
self.endpoints = APIEndpoints(
api_base,
LlamaAPI.CHAT_COMPLETIONS
)
self.exception_message = exception_message
async def make_request(self, payload: Payload) -> str:
data = payload.render()
async with aiohttp.ClientSession() as session:
try:
async with session.post(self.endpoints.chat_completion, json = data) as response:
if response.status == 200:
result = await response.json()
return result["choices"][0]["message"]["content"]
raise LanguageModelException(f"Request failed with status code {response.status}!")
except (aiohttp.ClientError, LanguageModelException) as e:
print(f"Exception occurred: {e}")
return self.exception_message
+40
View File
@@ -0,0 +1,40 @@
from dataclasses import dataclass
from .var import Chat, LLMParam, Who
@dataclass()
class Payload:
system_prompt: str
user_message: str
temperature: float
max_tokens: int
stream: bool
def render(self) -> dict:
return {
LLMParam.MESSAGES: [
{Chat.ROLE: Who.SYSTEM, Chat.CONTENT: self.system_prompt},
{Chat.ROLE: Who.USER, Chat.CONTENT: self.user_message}
],
LLMParam.TEMPERATURE: self.temperature,
LLMParam.MAX_TOKENS: self.max_tokens,
LLMParam.STREAM: self.stream
}
@dataclass
class APIEndpoints:
api_base: str
chat_completion: str
def __post_init__(self):
self.api_base = self.api_base.rstrip("/")
for field_name in self.__dataclass_fields__:
if field_name == "api_base":
continue
path = getattr(self, field_name)
clean_path = f"/{path.lstrip('/')}"
setattr(self, field_name, f"{self.api_base}{clean_path}")
+2
View File
@@ -0,0 +1,2 @@
class LanguageModelException(Exception):
pass
+23
View File
@@ -0,0 +1,23 @@
from enum import StrEnum
class LLMParam(StrEnum):
MESSAGES = "messages"
TEMPERATURE = "temperature"
MAX_TOKENS = "max_tokens"
STREAM = "stream"
class Chat(StrEnum):
ROLE = "role"
CONTENT = "content"
class Who(StrEnum):
SYSTEM = "system"
USER = "user"
ASSISTANT = "assistant"
class LlamaAPI(StrEnum):
CHAT_COMPLETIONS = "/v1/chat/completions"
class LanguageModelError(StrEnum):
GENERIC_EXCEPTION = "Language model server could not be contacted!"
+12
View File
@@ -0,0 +1,12 @@
aiohappyeyeballs==2.6.1
aiohttp==3.13.3
aiosignal==1.4.0
attrs==25.4.0
audioop-lts==0.2.2
discord==2.3.2
discord.py==2.6.4
frozenlist==1.8.0
idna==3.11
multidict==6.7.0
propcache==0.4.1
yarl==1.22.0