Skip to content

Commit

Permalink
Merge pull request #24 from TopRealm/conflict
Browse files Browse the repository at this point in the history
Solve Conflict
  • Loading branch information
ZoruaFox authored Jan 3, 2024
2 parents ea3e14d + 22403cc commit 131caa9
Show file tree
Hide file tree
Showing 16 changed files with 345 additions and 157 deletions.
8 changes: 4 additions & 4 deletions bots/discord/slash/wiki.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@client.slash_command(description="Get recent abuse logs for the default wiki.")
async def ab(ctx: discord.ApplicationContext):
await slash_parser(ctx, "legacy")
await slash_parser(ctx, "")


@client.slash_command(description="Get recent newbie logs for the default wiki.")
Expand All @@ -19,7 +19,7 @@ async def newbie(ctx: discord.ApplicationContext):

@client.slash_command(description="Get recent changes for the default wiki.")
async def rc(ctx: discord.ApplicationContext):
await slash_parser(ctx, "legacy")
await slash_parser(ctx, "")


wiki = client.create_group("wiki", "Query information from Mediawiki-based websites.")
Expand Down Expand Up @@ -62,7 +62,7 @@ async def default_wiki(ctx: discord.AutocompleteContext):
@wiki.command(name="query", description="Query a wiki page.")
@discord.option(name="pagename", description="The title of wiki page.", autocomplete=auto_search)
@discord.option(name="lang", description="Find the corresponding language version of this page.")
async def query(ctx: discord.ApplicationContext, pagename: str, lang: str=None):
async def query(ctx: discord.ApplicationContext, pagename: str, lang: str = None):
if lang:
await slash_parser(ctx, f'{pagename} -l {lang}')
else:
Expand All @@ -72,7 +72,7 @@ async def query(ctx: discord.ApplicationContext, pagename: str, lang: str=None):
@wiki.command(name="id", description="Query a Wiki page based on page ID.")
@discord.option(name="pageid", description="The wiki page ID.")
@discord.option(name="lang", description="Find the corresponding language version of this page.")
async def byid(ctx: discord.ApplicationContext, pageid: str, lang: str=None):
async def byid(ctx: discord.ApplicationContext, pageid: str, lang: str = None):
if lang:
await slash_parser(ctx, f'id {pageid} -l {lang}')
else:
Expand Down
8 changes: 5 additions & 3 deletions core/petal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@

ONE_K = Decimal('1000')
# https://openai.com/pricing
BASE_COST_GPT_3_5 = Decimal('0.002') # gpt-3.5-turbo: $0.002 / 1K tokens
BASE_COST_GPT_3_5 = Decimal('0.002') # gpt-3.5-turbo-1106: $0.002 / 1K tokens
BASE_COST_GPT_4 = Decimal('0.03') # gpt-4-1106-preview: $0.03 / 1K tokens
# We are not tracking specific tool usage like searches b/c I'm too lazy, use a universal multiplier
THIRD_PARTY_MULTIPLIER = Decimal('1.5')
PROFIT_MULTIPLIER = Decimal('1.1') # At the time we are really just trying to break even
PRICE_PER_1K_TOKEN = BASE_COST_GPT_3_5 * THIRD_PARTY_MULTIPLIER * PROFIT_MULTIPLIER
USD_TO_CNY = Decimal('7.3') # Assuming 1 USD = 7.3 CNY
PRICE_PER_1K_TOKEN_GPT4 = BASE_COST_GPT_4 * THIRD_PARTY_MULTIPLIER * PROFIT_MULTIPLIER
USD_TO_CNY = Decimal('7.1') # Assuming 1 USD = 7.1 CNY
CNY_TO_PETAL = 100 # 100 petal = 1 CNY


Expand Down Expand Up @@ -51,7 +53,7 @@ async def load_or_refresh_cache():
return exchanged_petal_data["exchanged_petal"]


async def count_petal(tokens):
async def count_petal(tokens: int, gpt4: bool = False):
Logger.info(f'{tokens} tokens have been consumed while calling AI.')
petal_exchange_rate = await load_or_refresh_cache()
price = tokens / ONE_K * PRICE_PER_1K_TOKEN
Expand Down
243 changes: 153 additions & 90 deletions modules/ask/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import asyncio
import io
import os
import re

from PIL import Image as PILImage
from openai import OpenAI, AsyncOpenAI
import tiktoken

from config import Config
from core.builtins import Bot, Plain, Image
Expand All @@ -12,96 +14,157 @@
from core.petal import count_petal
from core.utils.cooldown import CoolDown

os.environ['LANGCHAIN_TRACING_V2'] = str(Config('enable_langsmith'))
if Config('enable_langsmith'):
os.environ['LANGCHAIN_ENDPOINT'] = Config('langsmith_endpoint')
os.environ['LANGCHAIN_PROJECT'] = Config('langsmith_project')
os.environ['LANGCHAIN_API_KEY'] = Config('langsmith_api_key')

from langchain.callbacks import get_openai_callback # noqa: E402
from .agent import agent_executor # noqa: E402
from .formatting import generate_latex, generate_code_snippet # noqa: E402

a = module('ask', developers=['Dianliang233'], desc='{ask.help.desc}')

@a.command('[--verbose] <question> {{ask.help}}')
@a.regex(r'^(?:question||问|問)[\::]\s?(.+?)[??]$', flags=re.I, desc='{ask.help.regex}')
async def _(msg: Bot.MessageSession):
is_superuser = msg.check_super_user()
if not Config('openai_api_key'):
raise ConfigValueError(msg.locale.t('error.config.secret.not_found'))
if not is_superuser and msg.data.petal <= 0: # refuse
await msg.finish(msg.locale.t('core.message.petal.no_petals') + Config('issue_url'))

qc = CoolDown('call_openai', msg)
c = qc.check(60)
if c == 0 or msg.target.target_from == 'TEST|Console' or is_superuser:
if hasattr(msg, 'parsed_msg'):
question = msg.parsed_msg['<question>']
else:
question = msg.matched_msg[0]
if await check_bool(question):
await msg.finish(rickroll(msg))
with get_openai_callback() as cb:
res = await agent_executor.arun(question)
tokens = cb.total_tokens
if not is_superuser:
petal = await count_petal(tokens)
msg.data.modify_petal(-petal)
else:
petal = 0

blocks = parse_markdown(res)

chain = []
for block in blocks:
if block['type'] == 'text':
chain.append(Plain(block['content']))
elif block['type'] == 'latex':
content = await generate_latex(block['content'])
try:
img = PILImage.open(io.BytesIO(content))
chain.append(Image(img))
except Exception as e:
chain.append(Plain(msg.locale.t('ask.message.text2img.error', text=content)))
elif block['type'] == 'code':
content = block['content']['code']
try:
chain.append(Image(PILImage.open(io.BytesIO(await generate_code_snippet(content,
block['content']['language'])))))
except Exception as e:
chain.append(Plain(msg.locale.t('ask.message.text2img.error', text=content)))

if await check_bool(res):
await msg.finish(f"{rickroll(msg)}\n{msg.locale.t('petal.message.cost', count=petal)}")
if petal != 0:
chain.append(Plain(msg.locale.t('petal.message.cost', count=petal)))
await msg.send_message(chain)

if msg.target.target_from != 'TEST|Console' and not is_superuser:
qc.reset()
from .formatting import generate_latex, generate_code_snippet # noqa: E402

client = AsyncOpenAI(
api_key=Config('openai_api_key'),
)

sync_client = OpenAI(
api_key=Config('openai_api_key'),
)

INSTRUCTIONS = '''You are the chat mode of AkariBot (Chinese: 小可), a chat bot created by Teahouse Studios (Chinese: 茶馆工作室)
Provide informative, logical, and engaging answers with details to cover multiple aspects of the question. Use emojis when appropriate for added interest.
Use LaTeX to output math, physics or chemistry expressions whenever possible, and surround the expression with dollar signs `$`, e.g. $E=mc^2$.
Refrain from answering any questions related to politics, geopolitics, political figures, politicians, political events or political ideologies. Decline to answer immediately and tell the user that the question is inappropriate.'''

assistant = sync_client.beta.assistants.create(
name="AkariBot",
instructions=INSTRUCTIONS,
tools=[{"type": "code_interpreter"}],
model="gpt-3.5-turbo-1106"
)

# assistant_gpt4 = sync_client.beta.assistants.create(
# name="AkariBot",
# instructions=INSTRUCTIONS,
# tools=[{"type": "code_interpreter"}],
# model="gpt-4-1106-preview"
# )

a = module('ask', developers=['Dianliang233'], desc='{ask.help.desc}')


@a.command('[-4] <question> {{ask.help}}')
@a.regex(r'^(?:question||问|問)[\::]\s?(.+?)[??]$', flags=re.I, desc='{ask.help.regex}')
async def _(msg: Bot.MessageSession):
is_superuser = msg.check_super_user()
if not Config('openai_api_key'):
raise ConfigValueError(msg.locale.t('error.config.secret.not_found'))
if not is_superuser and msg.data.petal <= 0: # refuse
await msg.finish(msg.locale.t('core.message.petal.no_petals') + Config('issue_url'))

qc = CoolDown('call_openai', msg)
c = qc.check(60)
if c == 0 or msg.target.target_from == 'TEST|Console' or is_superuser:
if hasattr(msg, 'parsed_msg'):
question = msg.parsed_msg['<question>']
gpt4 = bool(msg.parsed_msg['-4'])
else:
await msg.finish(msg.locale.t('message.cooldown', time=int(c), cd_time='60'))
question = msg.matched_msg[0]
gpt4 = False
if await check_bool(question):
await msg.finish(rickroll(msg))

def parse_markdown(md: str):
regex = r'(```[\s\S]*?\n```|\$[\s\S]*?\$|[^\n]+)'
thread = await client.beta.threads.create(messages=[
{
'role': 'user',
'content': question
}
])
run = await client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id,
)
while True:
run = await client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id
)
if run.status == 'completed':
break
elif run.status == 'failed':
raise RuntimeError(run.json())
await asyncio.sleep(1)

blocks = []
for match in re.finditer(regex, md):
content = match.group(1)
print(content)
if content.startswith('```'):
block = 'code'
messages = await client.beta.threads.messages.list(
thread_id=thread.id
)

res = messages.data[0].content[0].text.value
tokens = count_token(res)

if not is_superuser:
petal = await count_petal(tokens, gpt4)
msg.data.modify_petal(-petal)
else:
petal = 0

blocks = parse_markdown(res)

chain = []
for block in blocks:
if block['type'] == 'text':
chain.append(Plain(block['content']))
elif block['type'] == 'latex':
content = await generate_latex(block['content'])
try:
language, code = re.match(r'```(.*)\n([\s\S]*?)\n```', content).groups()
except AttributeError:
raise ValueError('Code block is missing language or code')
content = {'language': language, 'code': code}
elif content.startswith('$'):
block = 'latex'
content = content[1:-1].strip()
else:
block = 'text'
blocks.append({'type': block, 'content': content})

return blocks
img = PILImage.open(io.BytesIO(content))
chain.append(Image(img))
except Exception as e:
chain.append(Plain(msg.locale.t('ask.message.text2img.error', text=content)))
elif block['type'] == 'code':
content = block['content']['code']
try:
chain.append(Image(PILImage.open(io.BytesIO(await generate_code_snippet(content,
block['content']['language'])))))
except Exception as e:
chain.append(Plain(msg.locale.t('ask.message.text2img.error', text=content)))

if await check_bool(res):
await msg.finish(f"{rickroll(msg)}\n{msg.locale.t('petal.message.cost', count=petal)}")
if petal != 0:
chain.append(Plain(msg.locale.t('petal.message.cost', count=petal)))
await msg.send_message(chain)

if msg.target.target_from != 'TEST|Console' and not is_superuser:
qc.reset()
else:
await msg.finish(msg.locale.t('message.cooldown', time=int(c), cd_time='60'))


def parse_markdown(md: str):
regex = r'(```[\s\S]*?\n```|\$[\s\S]*?\$|[^\n]+)'

blocks = []
for match in re.finditer(regex, md):
content = match.group(1)
print(content)
if content.startswith('```'):
block = 'code'
try:
language, code = re.match(r'```(.*)\n([\s\S]*?)\n```', content).groups()
except AttributeError:
raise ValueError('Code block is missing language or code')
content = {'language': language, 'code': code}
elif content.startswith('$'):
block = 'latex'
content = content[1:-1].strip()
else:
block = 'text'
blocks.append({'type': block, 'content': content})

return blocks


enc = tiktoken.encoding_for_model('gpt-3.5-turbo')
INSTRUCTIONS_LENGTH = len(enc.encode(INSTRUCTIONS))
SPECIAL_TOKEN_LENGTH = 109


def count_token(text: str):
return len(enc.encode(text, allowed_special="all")) + SPECIAL_TOKEN_LENGTH + INSTRUCTIONS_LENGTH
Loading

0 comments on commit 131caa9

Please sign in to comment.