stupid ai
parent
c0504863b2
commit
fb408c3c1e
Binary file not shown.
153
news/main.py
153
news/main.py
|
@ -14,6 +14,7 @@ from ollama import chat
|
||||||
from ollama import ChatResponse
|
from ollama import ChatResponse
|
||||||
from ollama import Client
|
from ollama import Client
|
||||||
from ollama import AsyncClient
|
from ollama import AsyncClient
|
||||||
|
import time
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
@ -34,75 +35,133 @@ logging.basicConfig(
|
||||||
|
|
||||||
article_repository = ArticleRepository()
|
article_repository = ArticleRepository()
|
||||||
|
|
||||||
async def send_chat(model, messages):
|
social_system_prompt = ("You are a specialized analysis program designed to determine if articles are pro-social. "
|
||||||
# return await AsyncClient(host="192.168.69.3:11434").chat(
|
"Pro-social text contains topics such as raising concerns about the negative effects on workers, the environment, "
|
||||||
# # model="deepseek-r1:1.5b",
|
"or on society as a whole (as in the concerns of the 99%, or the proletariat). "
|
||||||
# model="gemma3:12b-it-qat",
|
"You WILL give rating of this article by calling the increment tool if you read a paragraph (seperated by newlines) which is pro-society, and decrement if it is anti-society. "
|
||||||
# messages=messages,
|
"You WILL respond explaining why you have called the tools you did. "
|
||||||
# stream=False,
|
"You ARE allowed to answer with \"this article doesn't require social analysis\" if the topic is not relevant to social interests. "
|
||||||
# options={
|
"You ARE allowed to not make calls to your tools if the topic is not relevant to social interests. ")
|
||||||
# 'temperature': 0.5,
|
|
||||||
# # "num_ctx": 128000
|
capital_system_prompt = ("You are a specialized analysis program designed to determine if articles are pro-capital. "
|
||||||
# },
|
"Pro-capital text is concerned but not limited to the interests of business, or of the rich and elite. "
|
||||||
# think=False)
|
"You WILL give rating of this article by calling the increment tool if you read a paragraph (seperated by newlines) which is pro-capital, and decrement if it is anti-capital. "
|
||||||
return await AsyncClient(host="192.168.69.3:11434").generate(model=model, prompt=messages, stream=False)
|
"you ARE allowed to call the tools multiple times. "
|
||||||
|
"You WILL respond explaining why you have called the tools you did. "
|
||||||
|
"You ARE allowed to answer with \"this article doesn't require capital analysis\" if the topic is not relevant to capital interests. "
|
||||||
|
"You ARE allowed to not make calls to your tools if the topic is not relevant to capital interests. ")
|
||||||
|
|
||||||
|
facts_system_prompt = ("You are a specialized analysis program designed to determine if articles are attempting to accurately represent facts. "
|
||||||
|
"You are not checking if the facts presented in the article are correct, "
|
||||||
|
"rather you are to determine if an attempt was made to represent multiple sides of the issue. "
|
||||||
|
"This can range from only presenting the news in the form of events that happened or expert comments "
|
||||||
|
"(therefore not introducing the writer's opinion into the article), to not using too much emotional language "
|
||||||
|
"(emotional language can be fine, if for example the article is trying to communicate that one side has commited genocide, "
|
||||||
|
"it is okay to be emotional over that topic and should probably be encouraged. "
|
||||||
|
"If the article only presents opinions about genocide, then it is not accurately representing what happened). "
|
||||||
|
"You WILL give rating of this article by calling the increment tool if you read a paragraph (seperated by newlines) which is accurately representing facts, and decrement if it is not.")
|
||||||
|
|
||||||
|
async def send_chat(model, messages, tools = None):
|
||||||
|
return await AsyncClient(host="192.168.69.3:11434").chat(
|
||||||
|
model=model,
|
||||||
|
messages=messages,
|
||||||
|
stream=False,
|
||||||
|
tools=tools,
|
||||||
|
options={
|
||||||
|
'temperature': 0.5,
|
||||||
|
# "num_ctx": 128000
|
||||||
|
})
|
||||||
|
# return await AsyncClient(host="192.168.69.3:11434").generate(model=model, prompt=messages, stream=False)
|
||||||
|
|
||||||
|
async def send_chat_with_system(model, message, system, tools = None):
|
||||||
|
messages = [{'role': 'system', 'content': system}, {'role': 'user', 'content': message}]
|
||||||
|
return await send_chat(model, messages, tools)
|
||||||
|
|
||||||
async def send_text_file(channel: discord.abc.Messageable, content: str, message: str = "📄 Full article attached:", filename: str = "article.md") -> None:
|
async def send_text_file(channel: discord.abc.Messageable, content: str, message: str = "📄 Full article attached:", filename: str = "article.md") -> None:
|
||||||
fp = io.BytesIO(content.encode("utf-8"))
|
fp = io.BytesIO(content.encode("utf-8"))
|
||||||
file = discord.File(fp, filename=filename)
|
file = discord.File(fp, filename=filename)
|
||||||
await channel.send(message, file=file)
|
await channel.send(message, file=file)
|
||||||
|
|
||||||
|
def tally_responses(tools):
|
||||||
|
increment = 0
|
||||||
|
decrement = 0
|
||||||
|
if tools:
|
||||||
|
for tool in tools:
|
||||||
|
if tool['function']['name'] == "increment":
|
||||||
|
increment += 1
|
||||||
|
elif tool['function']['name'] == "decrement":
|
||||||
|
decrement += 1
|
||||||
|
else:
|
||||||
|
LOGGER.warning(f"Unknown tool: {tool}")
|
||||||
|
return increment, decrement
|
||||||
|
|
||||||
|
|
||||||
async def handle_article_url(message: discord.Message, url: str) -> None:
|
async def handle_article_url(message: discord.Message, url: str) -> None:
|
||||||
"""
|
|
||||||
Placeholder: download + analyse the article here.
|
|
||||||
|
|
||||||
Currently just acknowledges receipt so you can verify the event flow.
|
|
||||||
"""
|
|
||||||
LOGGER.info("Received URL from %s: %s", message.author, url)
|
LOGGER.info("Received URL from %s: %s", message.author, url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title, processed_html = await article_repository.get_article(url)
|
title, processed_html = await article_repository.get_article(url)
|
||||||
paragraphs = processed_html.split("\n")
|
|
||||||
paragraphs = [f"\"Paragraph ({i + 1})\": {paragraph.strip()}" for i, paragraph in enumerate(paragraphs)]
|
|
||||||
processed_graphs = [{"role": "user", "content": paragraph} for paragraph in paragraphs]
|
|
||||||
# print(paragraphs)
|
|
||||||
# print(processed_graphs)
|
|
||||||
|
|
||||||
# messages = [
|
tools = [
|
||||||
# {"role": "system", "content": "You are an expert article-analysis assistant."
|
{
|
||||||
# # "You WILL respond in JSON format."
|
'type': 'function',
|
||||||
# "Your job is to analyse paragraphs in the article and look for provocative, emotionally charged, and loaded language"
|
'function': {
|
||||||
# "You WILL analyse the paragraphs, determine if they are provocative, and if so, output a rating between 1 and 100, 100 being the most provocative."
|
'name': 'increment',
|
||||||
# "you WILL NOT output a summary of the article or the paragraphs."
|
'description': 'increment internal counter by 1',
|
||||||
# "Questions you should ask yourself while reading the paragraph:"
|
'parameters': {
|
||||||
# "1. What is the literal meaning of the questionable word or phrase?"
|
'type': 'object',
|
||||||
# "2. What is the emotional or social context of the questionable word or phrase?"
|
'properties': {},
|
||||||
# "3. Does that word or phrase have any connotations, that is, associations that are positive or negative?"
|
'required': []
|
||||||
# "4. What group (sometimes called a “discourse community”) favors one locution over another, and why?"
|
}
|
||||||
# "5. Is the word or phrase “loaded”? How far does it steer us from neutral?"
|
}
|
||||||
# "6. Does the word or phrase help me see, or does it prevent me from seeing? (This is important)"
|
},
|
||||||
# "You will now be provided with the headline of the article then a paragraph from the article."
|
{
|
||||||
# "The headline (title of the page) will be provided as \"Headline\": \"EXAMPLE HEADLINE\"."
|
'type': 'function',
|
||||||
# "The paragraphs will be provided as \"Paragraph (numbered index)\": \"EXAMPLE PARAGRAPH\"."},
|
'function': {
|
||||||
# {"role": "user", "content": f"\"Headline\": \"{title}\""}
|
'name': 'decrement',
|
||||||
# ]
|
'description': 'decrement internal counter by 1',
|
||||||
# messages.extend(processed_graphs)
|
'parameters': {
|
||||||
social = await send_chat("social", processed_html)
|
'type': 'object',
|
||||||
capital = await send_chat("capital", processed_html)
|
'properties': {},
|
||||||
facts = await send_chat("facts", processed_html)
|
'required': []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
social = await send_chat_with_system("social", processed_html, social_system_prompt, tools)
|
||||||
|
capital = await send_chat_with_system("capital", processed_html, capital_system_prompt, tools)
|
||||||
|
facts = await send_chat_with_system("facts", processed_html, facts_system_prompt, tools)
|
||||||
|
|
||||||
print(social)
|
print(social)
|
||||||
print(capital)
|
print(capital)
|
||||||
print(facts)
|
print(facts)
|
||||||
|
|
||||||
|
social_increment, social_decrement = tally_responses(social['message']["tool_calls"])
|
||||||
|
capital_increment, capital_decrement = tally_responses(capital['message']["tool_calls"])
|
||||||
|
facts_increment, facts_decrement = tally_responses(facts['message']["tool_calls"])
|
||||||
|
|
||||||
# TODO: parse `html`, summarise, etc.
|
# TODO: parse `html`, summarise, etc.
|
||||||
await message.channel.send(f"✅ Article downloaded – {len(processed_html):,} bytes.")
|
await message.channel.send(f"✅ Article downloaded – {len(processed_html):,} bytes.")
|
||||||
|
time.sleep(0.1)
|
||||||
await send_text_file(message.channel, processed_html)
|
await send_text_file(message.channel, processed_html)
|
||||||
await send_text_file(message.channel, social.response, "Social calculations:")
|
time.sleep(0.1)
|
||||||
await send_text_file(message.channel, capital.response, "capital calculations:")
|
await send_text_file(message.channel, social["message"]["content"], "Social calculations:")
|
||||||
await send_text_file(message.channel, facts.response, "facts calculations:")
|
time.sleep(0.1)
|
||||||
|
await message.channel.send(f"Social+ {social_increment} | Social- {social_decrement}")
|
||||||
|
time.sleep(0.1)
|
||||||
|
await send_text_file(message.channel, capital["message"]["content"], "capital calculations:")
|
||||||
|
time.sleep(0.1)
|
||||||
|
await message.channel.send(f"Capital+ {capital_increment} | Capital- {capital_decrement}")
|
||||||
|
time.sleep(0.1)
|
||||||
|
await send_text_file(message.channel, facts["message"]["content"], "facts calculations:")
|
||||||
|
time.sleep(0.1)
|
||||||
|
await message.channel.send(f"Facts+ {facts_increment} | Facts- {facts_decrement}")
|
||||||
|
time.sleep(0.1)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
await message.channel.send("❌ Sorry, an internal error has occurred. Please try again later or contact an administrator.")
|
await message.channel.send("❌ Sorry, an internal error has occurred. Please try again later or contact an administrator.")
|
||||||
await message.channel.send(f"```\n{exc}\n```")
|
await message.channel.send(f"```\n{exc}\n```")
|
||||||
|
LOGGER.error(exc, exc_info=True)
|
||||||
|
|
||||||
|
|
||||||
def extract_first_url(text: str) -> Optional[str]:
|
def extract_first_url(text: str) -> Optional[str]:
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
let
|
||||||
|
pkgs = import <nixpkgs> {};
|
||||||
|
in pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
(python3.withPackages (python-pkgs: with python-pkgs; [
|
||||||
|
ollama
|
||||||
|
requests
|
||||||
|
discordpy
|
||||||
|
python-dotenv
|
||||||
|
trafilatura
|
||||||
|
playwright
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
propagatedBuildInputs = with pkgs; [
|
||||||
|
xorg.libX11
|
||||||
|
xorg.libX11.dev
|
||||||
|
xorg.libXcursor
|
||||||
|
xorg.libXcursor.dev
|
||||||
|
xorg.libXext
|
||||||
|
xorg.libXext.dev
|
||||||
|
xorg.libXinerama
|
||||||
|
xorg.libXinerama.dev
|
||||||
|
xorg.libXrandr
|
||||||
|
xorg.libXrandr.dev
|
||||||
|
xorg.libXrender
|
||||||
|
xorg.libXrender.dev
|
||||||
|
xorg.libxcb
|
||||||
|
xorg.libxcb.dev
|
||||||
|
xorg.libXi
|
||||||
|
xorg.libXi.dev
|
||||||
|
harfbuzz
|
||||||
|
harfbuzz.dev
|
||||||
|
zlib
|
||||||
|
zlib.dev
|
||||||
|
bzip2
|
||||||
|
bzip2.dev
|
||||||
|
pngpp
|
||||||
|
brotli
|
||||||
|
brotli.dev
|
||||||
|
pulseaudio.dev
|
||||||
|
git
|
||||||
|
libGL
|
||||||
|
libGL.dev
|
||||||
|
glfw
|
||||||
|
];
|
||||||
|
LD_LIBRARY_PATH="/run/opengl-driver/lib:/run/opengl-driver-32/lib";
|
||||||
|
shellHook = ''
|
||||||
|
export PLAYWRIGHT_BROWSERS_PATH=${pkgs.playwright-driver.browsers}
|
||||||
|
export PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true
|
||||||
|
'';
|
||||||
|
}
|
Loading…
Reference in New Issue