This commit is contained in:
2025-01-11 20:59:10 -05:00
parent 85a0d6bc62
commit 3c57f13557
18 changed files with 464 additions and 365 deletions

View File

@ -1,25 +1,39 @@
#!/usr/bin/env python3.12
# pylint: disable=bare-except, broad-exception-caught, invalid-name
import importlib
import logging
import traceback
import regex
from aiohttp import ClientSession, ClientTimeout
from fastapi import FastAPI, Security, Request, HTTPException
from fastapi.security import APIKeyHeader, APIKeyQuery
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
from pydantic import BaseModel
class ValidAISongRequest(BaseModel):
"""
- **a**: artist
- **s**: track title
"""
a: str
s: str
class ValidHookSongRequest(BaseModel):
"""
- **a**: artist
- **s**: track title
- **hook**: hook to return
"""
a: str
s: str
hook: str | None = ""
# pylint: enable=bad-indentation
class AI(FastAPI):
"""AI Endpoints"""
"""AI Endpoints"""
def __init__(self, app: FastAPI, my_util, constants, glob_state): # pylint: disable=super-init-not-called
self.app = app
self.util = my_util
@ -29,12 +43,48 @@ class AI(FastAPI):
self.endpoints = {
"ai/openai": self.ai_openai_handler,
"ai/base": self.ai_handler,
"ai/song": self.ai_song_handler
"ai/song": self.ai_song_handler,
"ai/hook": self.ai_hook_handler,
#tbd
}
}
for endpoint, handler in self.endpoints.items():
app.add_api_route(f"/{endpoint}/{{any:path}}", handler, methods=["POST"])
app.add_api_route(f"/{endpoint}/openai/", handler, methods=["POST"])
async def respond_via_webhook(self, data: ValidHookSongRequest, originalRequest: Request):
"""Respond via Webhook"""
try:
logging.debug("Request received: %s", data)
data2 = data.copy()
del data2.hook
response = await self.ai_song_handler(data2, originalRequest)
if not response.get('resp'):
logging.critical("NO RESP!")
return
response = response.get('resp')
hook_data = {
'username': 'Claude',
"embeds": [{
"title": "Claude's Feedback",
"description": response,
"footer": {
"text": "Current model: claude-3-haiku-20240307",
}
}]
}
logging.critical("Request: %s", data)
async with ClientSession() as session:
async with session.post(data.hook, json=hook_data,
timeout=ClientTimeout(connect=5, sock_read=5), headers={
'content-type': 'application/json; charset=utf-8',}) as request:
logging.debug("Returned: %s",
await request.json())
await request.raise_for_status()
return True
except:
traceback.print_exc()
return False
async def ai_handler(self, request: Request):
"""
@ -42,83 +92,80 @@ class AI(FastAPI):
AI BASE Request
(Requires key)
"""
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With')):
raise HTTPException(status_code=403, detail="Unauthorized")
local_llm_headers = {
'Authorization': f'Bearer {self.constants.LOCAL_LLM_KEY}'
}
forward_path = self.url_clean_regex.sub('', request.url.path)
try:
async with ClientSession() as session:
async with await session.post(f'{self.constants.LOCAL_LLM_BASE}/{forward_path}',
json=await request.json(),
headers=local_llm_headers,
timeout=ClientTimeout(connect=15, sock_read=30)) as out_request:
await self.glob_state.increment_counter('ai_requests')
response = await out_request.json()
return response
async with ClientSession() as session:
async with await session.post(f'{self.constants.LOCAL_LLM_BASE}/{forward_path}',
json=await request.json(),
headers=local_llm_headers,
timeout=ClientTimeout(connect=15, sock_read=30)) as out_request:
await self.glob_state.increment_counter('ai_requests')
response = await out_request.json()
return response
except Exception as e: # pylint: disable=broad-exception-caught
logging.error("Error: %s", e)
return {
'err': True,
'errorText': 'General Failure'
}
logging.error("Error: %s", e)
return {
'err': True,
'errorText': 'General Failure'
}
async def ai_openai_handler(self, request: Request):
"""
/ai/openai/
AI Request
(Requires key)
"""
if not self.util.check_key(request.url.path, request.headers.get('X-Authd-With')):
raise HTTPException(status_code=403, detail="Unauthorized")
"""
TODO: Implement Claude
Currently only routes to local LLM
"""
local_llm_headers = {
'Authorization': f'Bearer {self.constants.LOCAL_LLM_KEY}'
}
forward_path = self.url_clean_regex.sub('', request.url.path)
try:
async with ClientSession() as session:
async with await session.post(f'{self.constants.LOCAL_LLM_HOST}/{forward_path}',
json=await request.json(),
headers=local_llm_headers,
timeout=ClientTimeout(connect=15, sock_read=30)) as out_request:
await self.glob_state.increment_counter('ai_requests')
response = await out_request.json()
return response
async with ClientSession() as session:
async with await session.post(f'{self.constants.LOCAL_LLM_HOST}/{forward_path}',
json=await request.json(),
headers=local_llm_headers,
timeout=ClientTimeout(connect=15, sock_read=30)) as out_request:
await self.glob_state.increment_counter('ai_requests')
response = await out_request.json()
return response
except Exception as e: # pylint: disable=broad-exception-caught
logging.error("Error: %s", e)
return {
'err': True,
'errorText': 'General Failure'
}
logging.error("Error: %s", e)
return {
'err': True,
'errorText': 'General Failure'
}
"""
CLAUDE BELOW, COMMENTED
"""
async def ai_hook_handler(self, data: ValidHookSongRequest, request: Request, background_tasks: BackgroundTasks):
"""AI Hook Handler"""
background_tasks.add_task(self.respond_via_webhook, data, request)
return {
'success': True,
}
async def ai_song_handler(self, data: ValidAISongRequest, request: Request):
"""
/ai/song/
AI (Song Info) Request [Public]
"""
ai_prompt = "You are a helpful assistant who will provide tidbits of info on songs the user may listen to."
ai_question = f"I am going to listen to the song \"{data.s}\" by \"{data.a}\"."
local_llm_headers = {
'x-api-key': self.constants.CLAUDE_API_KEY,
'anthropic-version': '2023-06-01',
@ -126,93 +173,42 @@ class AI(FastAPI):
}
request_data = {
'model': 'claude-3-haiku-20240307',
'max_tokens': 512,
'temperature': 0.6,
'system': ai_prompt,
'messages': [
{
"role": "user",
"content": ai_question.strip(),
}
]
'model': 'claude-3-haiku-20240307',
'max_tokens': 512,
'temperature': 0.6,
'system': ai_prompt,
'messages': [
{
"role": "user",
"content": ai_question.strip(),
}
]
}
try:
async with ClientSession() as session:
async with await session.post('https://api.anthropic.com/v1/messages',
json=request_data,
headers=local_llm_headers,
timeout=ClientTimeout(connect=15, sock_read=30)) as request:
await self.glob_state.increment_counter('claude_ai_requests')
response = await request.json()
print(f"Response: {response}")
if response.get('type') == 'error':
error_type = response.get('error').get('type')
error_message = response.get('error').get('message')
result = {
'resp': f"{error_type} error ({error_message})"
}
else:
result = {
'resp': response.get('content')[0].get('text').strip()
}
return result
async with ClientSession() as session:
async with await session.post('https://api.anthropic.com/v1/messages',
json=request_data,
headers=local_llm_headers,
timeout=ClientTimeout(connect=15, sock_read=30)) as request:
await self.glob_state.increment_counter('claude_ai_requests')
response = await request.json()
logging.debug("Response: %s",
response)
if response.get('type') == 'error':
error_type = response.get('error').get('type')
error_message = response.get('error').get('message')
result = {
'resp': f"{error_type} error ({error_message})"
}
else:
result = {
'resp': response.get('content')[0].get('text').strip()
}
return result
except Exception as e: # pylint: disable=broad-exception-caught
logging.error("Error: %s", e)
return {
'err': True,
'errorText': 'General Failure'
}
# async def ai_song_handler(self, data: ValidAISongRequest, request: Request):
# """
# /ai/song/
# AI (Song Info) Request [Public]
# """
# ai_question = f"I am going to listen to the song \"{data.s}\" by \"{data.a}\"."
# local_llm_headers = {
# 'Authorization': f'Bearer {self.constants.LOCAL_LLM_KEY}'
# }
# ai_req_data = {
# 'max_context_length': 8192,
# 'max_length': 512,
# 'temperature': 0,
# 'n': 1,
# 'top_k': 30,
# 'top_a': 0,
# 'top_p': 0,
# 'typical': 0,
# 'mirostat': 0,
# 'use_default_badwordsids': False,
# 'rep_pen': 1.0,
# 'rep_pen_range': 320,
# 'rep_pen_slope': 0.05,
# 'quiet': 1,
# 'bypass_eos': False,
# # 'trim_stop': True,
# 'sampler_order': [6,0,1,3,4,2,5],
# 'memory': "You are a helpful assistant who will provide ONLY TOTALLY ACCURATE tidbits of info on songs the user may listen to. You do not include information about which album a song was released on, or when it was released, and do not mention that you are not including this information in your response. If the input provided is not a song you are aware of, simply state that. Begin your output at your own response.",
# 'stop': ['### Inst', '### Resp'],
# 'prompt': ai_question
# }
# try:
# async with ClientSession() as session:
# async with await session.post(f'{self.constants.LOCAL_LLM_BASE}/generate',
# json=ai_req_data,
# headers=local_llm_headers,
# timeout=ClientTimeout(connect=15, sock_read=30)) as request:
# await self.glob_state.increment_counter('ai_requests')
# response = await request.json()
# result = {
# 'resp': response.get('results')[0].get('text').strip()
# }
# return result
# except Exception as e: # pylint: disable=broad-exception-caught
# logging.error("Error: %s", e)
# return {
# 'err': True,
# 'errorText': 'General Failure'
# }
logging.error("Error: %s", e)
return {
'err': True,
'errorText': 'General Failure'
}

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3.12
from fastapi import FastAPI
from pydantic import BaseModel
@ -31,7 +30,7 @@ class Counters(FastAPI):
self.endpoints = {
"counters/get": self.get_counter_handler,
"counters/increment": self.increment_counter_handler
# "counters/increment": self.increment_counter_handler
#tbd
}
@ -55,14 +54,12 @@ class Counters(FastAPI):
}
async def increment_counter_handler(self, data: ValidCounterIncrementRequest):
"""
/counters/increment/
Increment counter value (requires PUT KEY)
"""
return {
}
# async def increment_counter_handler(self, data: ValidCounterIncrementRequest):
# """
# /counters/increment/
# Increment counter value (requires PUT KEY)
# """
# return {
# }

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python3.12
# pylint: disable=bare-except, broad-exception-caught
import os
import logging
import time
import datetime
import traceback
import aiosqlite as sqlite3
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
@ -36,31 +38,35 @@ class ValidTopKarmaRequest(BaseModel):
n: int | None = 10
class KarmaDB:
"""Karma DB Util"""
def __init__(self):
self.db_path = os.path.join("/", "var", "lib", "singerdbs", "karma.db")
async def get_karma(self, keyword: str) -> int | dict:
"""Get Karma Value for Keyword"""
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
async with db_conn.execute("SELECT score FROM karma WHERE keyword LIKE ? LIMIT 1", (keyword,)) as db_cursor:
try:
(score,) = await db_cursor.fetchone()
return score
except TypeError as e:
except TypeError:
return {
'err': True,
'errorText': f'No records for {keyword}',
}
async def get_top(self, n: int = 10):
"""Get Top n=10 Karma Entries"""
try:
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
async with db_conn.execute("SELECT keyword, score FROM karma ORDER BY score DESC LIMIT ?", (n,)) as db_cursor:
return await db_cursor.fetchall()
except Exception as e:
print(traceback.format_exc())
except:
traceback.print_exc()
return
async def update_karma(self, granter: str, keyword: str, flag: int):
"""Update Karma for Keyword"""
if not flag in [0, 1]:
return
@ -72,7 +78,7 @@ class KarmaDB:
audit_query = "INSERT INTO karma_audit(impacted_keyword, comment) VALUES(?, ?)"
now = int(time.time())
print(f"Audit message: {audit_message}\nKeyword: {keyword}")
logging.debug("Audit message: %s{audit_message}\nKeyword: %s{keyword}")
async with sqlite3.connect(self.db_path, timeout=2) as db_conn:
async with db_conn.execute(audit_query, (keyword, audit_message,)) as db_cursor:
@ -133,8 +139,8 @@ class Karma(FastAPI):
try:
top10 = await self.db.get_top(n=n)
return top10
except Exception as e:
print(traceback.format_exc())
except:
traceback.print_exc()
return {
'err': True,
'errorText': 'Exception occurred.',
@ -158,7 +164,7 @@ class Karma(FastAPI):
'count': count,
}
except:
print(traceback.format_exc())
traceback.print_exc()
return {
'err': True,
'errorText': "Exception occurred."
@ -182,5 +188,4 @@ class Karma(FastAPI):
return {
'success': await self.db.update_karma(data.granter, data.keyword, data.flag)
}
}

View File

@ -10,13 +10,13 @@ class ValidArtistSearchRequest(BaseModel):
"""
a: str
class Config:
schema_extra = {
"example": {
"a": "eminem"
}
}
class Config: # pylint: disable=missing-class-docstring
schema_extra = {
"example": {
"a": "eminem"
}
}
class ValidAlbumDetailRequest(BaseModel):
"""
@ -27,12 +27,12 @@ class ValidAlbumDetailRequest(BaseModel):
a: str
a2: str
class Config:
schema_extra = {
"example": {
"a": "eminem",
"a2": "houdini"
}
class Config: # pylint: disable=missing-class-docstring
schema_extra = {
"example": {
"a": "eminem",
"a2": "houdini"
}
}
class ValidTrackInfoRequest(BaseModel):
@ -44,12 +44,12 @@ class ValidTrackInfoRequest(BaseModel):
a: str
t: str
class Config:
schema_extra = {
"example": {
"a": "eminem",
"t": "rap god"
}
class Config: # pylint: disable=missing-class-docstring
schema_extra = {
"example": {
"a": "eminem",
"t": "rap god"
}
}
class LastFM(FastAPI):
@ -185,11 +185,11 @@ class LastFM(FastAPI):
artist = data.a
track = data.t
if not(artist) or not(track):
return {
if not artist or not track:
return {
'err': True,
'errorText': 'Invalid request'
}
}
track_info_result = await self.lastfm.get_track_info(artist=artist, track=track)
assert not "err" in track_info_result.keys()

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python3.12
# pylint: disable=bare-except, broad-exception-raised, broad-exception-caught
import importlib
import traceback
import logging
import urllib.parse
import regex
import aiohttp
import traceback
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
@ -92,6 +94,7 @@ class LyricSearch(FastAPI):
}
async def lyric_search_log_handler(self, data: ValidLyricSearchLogRequest):
"""Lyric Search Log Handler"""
include_radio = data.webradio
await self.glob_state.increment_counter('lyrichistory_requests')
last_10k_sings = await self.lyrics_engine.getHistory(limit=10000, webradio=include_radio)
@ -181,7 +184,8 @@ class LyricSearch(FastAPI):
lrc_content = response_json.get('syncedLyrics')
returned_artist = response_json.get('artistName')
returned_song = response_json.get('trackName')
print(f"Synced Lyrics [LRCLib]: {lrc_content}")
logging.debug("Synced Lyrics [LRCLib]: %s",
lrc_content)
lrc_content_out = []
for line in lrc_content.split("\n"):
_timetag = None
@ -192,7 +196,8 @@ class LyricSearch(FastAPI):
if not reg_helper:
continue
reg_helper = reg_helper[0]
print(f"Reg helper: {reg_helper} for line: {line}; len: {len(reg_helper)}")
logging.debug("Reg helper: %s for line: %s; len: %s",
reg_helper, line, len(reg_helper))
_timetag = reg_helper[0]
if not reg_helper[1].strip():
_words = ""
@ -214,7 +219,7 @@ class LyricSearch(FastAPI):
'reqn': await self.glob_state.get_counter('lyric_requests'),
}
except:
print(traceback.format_exc())
traceback.print_exc()
return {
'err': True,
'errorText': 'Search failed!',
@ -225,6 +230,7 @@ class LyricSearch(FastAPI):
'err': True,
'errorText': 'Search failed!',
}
if lrc:
return {
'err': False,
'artist': search_worker['artist'],
@ -236,7 +242,7 @@ class LyricSearch(FastAPI):
'reqn': await self.glob_state.get_counter('lyric_requests'),
}
search_worker = await self.lyrics_engine.lyrics_worker(searching=search_object, recipient='anyone')
search_worker = await self.lyrics_engine.lyrics_worker(searching=search_object)
if not search_worker or not 'l' in search_worker.keys():

View File

@ -2,11 +2,9 @@
import os
import random
import traceback
import aiosqlite as sqlite3
from typing import Optional
from fastapi import FastAPI, Request
import aiosqlite as sqlite3
from fastapi import FastAPI
from pydantic import BaseModel
class RandMsgRequest(BaseModel):

View File

@ -56,7 +56,7 @@ class Transcriptions(FastAPI):
show_id = int(show_id)
if not(str(show_id).isnumeric()) or not(show_id in [0, 1, 2]):
if not(str(show_id).isnumeric()) or show_id not in [0, 1, 2]:
return {
'err': True,
'errorText': 'Show not found.'
@ -99,7 +99,7 @@ class Transcriptions(FastAPI):
"""
show_id = data.s
episode_id = data.e
# pylint: disable=line-too-long
match show_id:
case 0:
db_path = os.path.join("/", "var", "lib", "singerdbs", "sp.db")

View File

@ -1,21 +1,9 @@
#!/usr/bin/env python3.12
# pylint: disable=invalid-name
from fastapi import FastAPI, Request, HTTPException, WebSocket, WebSocketDisconnect, WebSocketException
from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
from aiohttp import ClientSession, ClientTimeout
from aces.connection_manager import ConnectionManager
from aces.flac_reader import AudioStreamer
import os
import asyncio
import pyaudio
import wave
import traceback
import pyflac.decoder as decoder
import numpy as np
import soundfile as sf
import json
import time
class ValidXCRequest(BaseModel):
"""
@ -82,7 +70,7 @@ class XC(FastAPI):
1: '10.10.10.100:5992' # MS & Waleed Combo
}
if not bid in BID_ADDR_MAP.keys():
if not bid in BID_ADDR_MAP:
return {
'err': True,
'errorText': 'Invalid bot id'

View File

@ -42,6 +42,4 @@ class YT(FastAPI):
return {
'video_id': yt_video_id,
'extras': yts_res[0]
}
}