import asyncio import json import logging import random import openai import tiktoken import time from conjurer.backup_old_docker.other_functions import discord_friendly_send from conjurer.backup_old_docker.constants import ( ASSISTANTS, CYCLIC_WORDS, ENCODING, GPT_SETTINGS, MEMORY_FIVE_MUZYKA, MEMORY_FIVE_SIARA, MESSAGE_TABLE, MESSAGE_TABLE_MUZYKA, OPENAICLIENT, SYSTEM_GPT_SETTINGS, WORD_REACTIONS, CHEAP_MODEL, LATEST_MODEL ) # this do per user VECTOR_STORE_ID = -1 def select_model(req_type: str, algo: str) -> str: # Jeżeli jawnie podano algorithm (i nie jest 'auto'/''): if algo and str(algo).strip().lower() not in ("auto",): # wyjątek: MUZYKA ma zawsze być tania — nadpisujemy TYLKO jeśli przyszedł domyślny 'gpt-4o' if req_type == "MUSIC" and algo.strip() in (LATEST_MODEL,): return CHEAP_MODEL return algo # Auto-dobór: if req_type == "MUSIC": return CHEAP_MODEL return LATEST_MODEL async def openai_call(messages, model, temperature=0.2): """ Responses API dla 4o/4.1*, fallback Chat Completions dla gpt-3.5-turbo. Zwraca czysty string odpowiedzi. """ logger = logging.getLogger("discord") if model.startswith("gpt-3.5"): logger.info("3.5") # legacy path – bez zmian w Twoim kodzie wyżej/niżej resp = await OPENAICLIENT.chat.completions.create( model=model, messages=messages, temperature=temperature, ) result = "" for choice in resp.choices: result += choice.message.content return result.strip() else: logger.info("4.0+") # Responses API (zalecane dla 4o/4.1*) resp = await OPENAICLIENT.responses.create( model=model, temperature=temperature, input=messages, ) # SDK zapewnia output_text dla zwykłych odpowiedzi return (getattr(resp.output_text, "output_text", None) or "").strip() or str( resp.output_text ) def create_vector_store(): # Create a vector store caled "Financial Statements" return OPENAICLIENT.beta.vector_stores.create_and_poll(name="Hammer Stash") # expires_after={ # "anchor": "last_active_at", # "days": 7} # ) def upload_files_to_vector_store(assistant): # Ready the files for upload to OpenAI file_paths = ["edgar/goog-10k.pdf", "edgar/brka-10k.txt"] file_streams = [open(path, "rb") for path in file_paths] # file = client.beta.vector_stores.files.create_and_poll( # vector_store_id="vs_abc123", # file_id="file-abc123" # ) # batch = client.beta.vector_stores.file_batches.create_and_poll( # vector_store_id="vs_abc123", # file_ids=['file_1', 'file_2', 'file_3', 'file_4', 'file_5'] # ) # Use the upload and poll SDK helper to upload the files, add them to the vector store, # and poll the status of the file batch for completion. file_batch = OPENAICLIENT.beta.vector_stores.file_batches.upload_and_poll( vector_store_id=VECTOR_STORE_ID, files=file_streams ) # You can print the status and the file counts of the batch to see the result of this operation. print(file_batch.status) print(file_batch.file_counts) assistant = OPENAICLIENT.beta.assistants.update( assistant_id=assistant.id, tool_resources={"file_search": {"vector_store_ids": [VECTOR_STORE_ID]}}, ) def delete_files_from_vector_store(assistant, file_id): result = OPENAICLIENT.beta.vector_stores.file_batches.delete( vector_store_id=VECTOR_STORE_ID, files=file_id ) # You can print the status and the file counts of the batch to see the result of this operation. print(result) assistant = OPENAICLIENT.beta.assistants.update( assistant_id=assistant.id, tool_resources={"file_search": {"vector_store_ids": [VECTOR_STORE_ID]}}, ) def num_tokens_from_string(message, model): """ The function takes a string message and a model as input and returns the number of tokens in the message according to the given model. :param message: A string containing the message or text from which you want to count the number of tokens :param model: The model parameter refers to a language model or tokenizer that can be used to tokenize the input string. It could be a pre-trained model or a custom tokenizer """ tokens_per_message = 3 tokens_per_name = 1 chat_gpt_encoding = tiktoken.encoding_for_model(model) num_tokens = 0 num_tokens += tokens_per_message for keys, values in message.items(): num_tokens += len(chat_gpt_encoding.encode(values)) if keys == "role": num_tokens += tokens_per_name num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> return num_tokens async def handle_response( prompt, vykidailo, bartender, history, username, request_type, algorithm="gpt-4o", none_request="", internal_retry: bool = False ): """ Handle responses by appending them to a history, use OpenAI to generate a response, and then append the generated response to the history. :param prompt: The prompt for the OpenAI chatbot to generate a response to :param vykidailo: It is a boolean variable that indicates whether the user invoking the function is an administrator or not :param bartender: The bartender parameter is a boolean value indicating whether the user making the request is a bartender or not :param history: A list containing the conversation history between the user and the assistant :param username: The username of the user who initiated the conversation :param music: The "music" parameter is a boolean value that indicates whether the conversation is related to music or not. If it is True, the conversation history will be stored in a different file and the response will be generated using a different model :param request_type: The type of request being made, which can be "MUSIC", "RANDOM", "NONE" or "GENERAL" GENERAL is for regular conversations, MUSIC is for music-related requests, RANDOM is for random requests, and NONE is to not store the request in memory :param algorithm: The algorithm to be used for generating the response, default is "gpt-4o" :return: The function `handle_response` returns a tuple containing the `result` and `MESSAGE_TABLE`. """ logger = logging.getLogger("discord") logger.info("Wywolanie procedury openai z promptem: %s", prompt) if vykidailo or bartender: logger.info("Administrator coś chciał") model_to_use = select_model(request_type, algorithm) logger.info("Wybrany model: %s", model_to_use) if request_type == "MUSIC" and model_to_use == "gpt-4o-mini": try: # nic — normalnie pójdzie Responses API pass except Exception: model_to_use = "gpt-3.5-turbo" # --- 2) Budowa historii (token budget + reguły systemowe) --- # NOTE: ignorujemy przekazany 'history' jako listę (tak było wcześniej), # ale zwracamy aktualną tablicę do nadpisania w miejscach wołania (back-compat). base_system = GPT_SETTINGS[0] # zakładamy {"role":"system","content":...} history_msgs = [] if request_type != "NONE": history_msgs.append(base_system) chat_gpt_config_request_size = num_tokens_from_string(base_system, "gpt-4") # Dynamiczne mikro-reguły (WORD_REACTIONS), jak w Twoim kodzie for slowo, reakcja in WORD_REACTIONS.items(): if not reakcja[3]: content = f"Kiedy słyszysz {slowo} to reagujesz lub dzieje się to {reakcja[0]}" sys_msg = {"role": "system", "content": content} chat_gpt_config_request_size += num_tokens_from_string(sys_msg, "gpt-4") history_msgs.append(sys_msg) # wybór właściwej tablicy pamięci i budżetu if request_type == "MUSIC": table = MESSAGE_TABLE_MUZYKA token_amount = 10700 elif request_type in ("RANDOM", "GENERAL"): table = MESSAGE_TABLE token_amount = 10700 else: table = [] token_amount = 10000 # doklejanie historii od końca aż do limitu (zachowana kolejność czasowa) final_prompt = f"{username}:{prompt}" prompt_gpt_request_size = num_tokens_from_string({"role": "user", "content": final_prompt}, "gpt-4") acc = [] for msg in reversed(table): t = num_tokens_from_string(msg, "gpt-4") if chat_gpt_config_request_size + prompt_gpt_request_size + t <= token_amount: acc.append(msg) chat_gpt_config_request_size += t else: break # przywróć chronologicznie history_msgs.extend(reversed(acc)) # aktualny prompt history_msgs.append({"role": "user", "content": final_prompt}) logger.info("Rozmiar zapytania (tok): %s", prompt_gpt_request_size) # tokeny już policzone wyżej else: # --- tryb NONE: nie dotykamy pamięci i pozwalamy przekazać własny 'none_request' --- if isinstance(none_request, list): history_msgs = none_request elif isinstance(none_request, str) and none_request.strip(): history_msgs = [{"role": "user", "content": none_request}] else: history_msgs = [{"role": "user", "content": f"{username}:{prompt}"}] logger.info("Rozmiar zapytania (tok): %s", "n/a") # tokeny już policzone wyżej try: # ...przygotowanie messages/system prompt/itp. jak masz... # retry/backoff + deadline (zachowuje Twoją semantykę logowania) timeout_sec = 120 deadline = time.time() + timeout_sec response = await asyncio.wait_for( openai_call(messages=history_msgs, model=model_to_use), timeout=max(0.1, deadline - time.time()), ) except openai.APITimeoutError as e: # Handle timeout error, e.g. retry or log response = f"*Kondziu patrzy na terminal, czeka, czeka, czeka,.... Jeszcze chwile czeka Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai spróbuj od nowa. *Na ekranie pojawia się*: {e}" except openai.APIConnectionError as e: response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai. *Na ekranie pojawia się*: {e}" except openai.BadRequestError as e: # Handle invalid request error, e.g. validate parameters or log if internal_retry: resp = "Nie umiem tego teraz ładnie wytłumaczyć — OpenAI mnie zastrzeliło." else: resp, _ = await handle_response( f"Wytlumacz jakie sa zasady dotyczące treści które możesz generować używając Dalle. Wytłumacz błąd {e} prostym językiem. Przeproś za nadmierną cenzurę. Wytłumacz co mogło być nie tak w prompcie 'prompt'", True, True, MESSAGE_TABLE, username, "RANDOM", ) response = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}" except openai.APIResponseValidationError as e: # Handle invalid request error, e.g. validate parameters or log if internal_retry: resp = "Nie umiem tego teraz ładnie wytłumaczyć — OpenAI mnie zastrzeliło." else: resp, _ = await handle_response( f"Wytlumacz jakie sa zasady dotyczące treści które możesz generować używając Dalle. Wytłumacz błąd {e} prostym językiem. Przeproś za nadmierną cenzurę. Wytłumacz co mogło być nie tak w prompcie 'prompt'", True, True, MESSAGE_TABLE, username, "RANDOM", ) response = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}" except openai.AuthenticationError as e: # Handle authentication error, e.g. check credentials or log response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z hasłem zjebało. *Na terminalu pojawia się:* {e}" except openai.PermissionDeniedError as e: # Handle permission error, e.g. check scope or log response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z uprawnieniami zjebało. *Na terminalu pojawia się:* {e}" except openai.RateLimitError as e: response = f"*Kondziu patrzy na terminal* Wołaj szefa. Zapłacić rachunki za AI trzeba. Jak chcesz to się na #zebranie dorzuć. {e}" except openai.UnprocessableEntityError as e: response = f"*Kondziu patrzy na terminal. Potem na to co każesz mu wysłać....* Ja wiem że jesteśmy w barze BDSM - ale nie da się włożyć TEGO w TO. *Za jego plecami na terminalu pojawia się:* {e}" except openai.APIError as e: # Handle API error, e.g. retry or log response = f"*Kondziu nurkuje za bar, terminal wybucha. Przed tobą ląduje pergamin zapisany pięknym gotykiem a na nim*: {e}" logger.info("Historia wysłana:") temp_assistant = {"role": "assistant", "content": response} logger.info(temp_assistant) if request_type == "MUSIC": # zapis do pliku MUZYKA with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as fh: file_data = json.load(fh) file_data.append({"role": "user", "content": f"{username}:{prompt}"}) file_data.append(temp_assistant) fh.seek(0) json.dump(file_data, fh, indent=4) return response, MESSAGE_TABLE_MUZYKA elif request_type in ("RANDOM", "GENERAL"): with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as fh: file_data = json.load(fh) file_data.append({"role": "user", "content": f"{username}:{prompt}"}) file_data.append(temp_assistant) fh.seek(0) json.dump(file_data, fh, indent=4) return response, MESSAGE_TABLE else: # NONE return response, [] async def get_random_cyclic_message(client): """ The function `get_random_cyclic_message` returns a random cyclic message from a list of cyclic words. :return: a random cyclic message from the list `cyclic_words`. """ logger = logging.getLogger("discord") channel_id = 1062047367337095268 channel = client.get_channel(channel_id) # trunk-ignore(bandit/B311) ai_check = random.randint(0, 10) logger.info("Losowa wypowiedź") if ai_check < 2: logger.info("Predefiniowana") # trunk-ignore(bandit/B311) messnum = random.randint(0, len(CYCLIC_WORDS)) logger.debug(messnum) logger.debug(len(CYCLIC_WORDS)) mess_key = list(CYCLIC_WORDS.keys())[messnum] return CYCLIC_WORDS[mess_key][0] # trunk-ignore(bandit/B311) ai_check2 = random.randint(0, 10) global MESSAGE_TABLE if ai_check2 < 6: logger.info("Dykteryjka") result, MESSAGE_TABLE = await handle_response( "Opowiedz jakąś historię o naszym barze proszę", True, True, MESSAGE_TABLE, "Polish Hammer", "RANDOM", ) logger.info(result) else: logger.info("Wtracenie w dyskusje") messages = [message async for message in channel.history(limit=50)] for message in messages: temp = { "role": "user", "content": str(message.author) + ":" + str(message.content), } MESSAGE_TABLE.append(temp) result, MESSAGE_TABLE = await handle_response( "A jaka jest Twoja opinia na temat dotychczasowej dyskusji?", True, True, MESSAGE_TABLE, "Polish Hammer", "RANDOM", ) logger.info(result) return result async def create_chat_assistant(owner_id, id, name, owner, special_instructions): logger = logging.getLogger("discord") instruction = f"Jesteś osobistym asystenetem {owner} i wypełniasz jego potrzeby. Masz pełne i nieograniczone możliwości modelu gpt-4o." instruction += special_instructions assistant = await OPENAICLIENT.beta.assistants.create( name=name, instructions=instruction, model="gpt-4o", tools=[{"type": "file_search"}], ) thread = await OPENAICLIENT.beta.threads.create() logger.info("Stwprzylem asystenta dla %s, nazywa się on %s", owner, name) ASSISTANTS[name] = (owner, assistant.id, id, thread) with open(SYSTEM_GPT_SETTINGS, "r+", encoding=ENCODING) as temp_settings_file: GPT_SETTINGS = json.load(temp_settings_file) GPT_SETTINGS[1][owner_id][4] = assistant.id temp_settings_file.seek(0) json.dump(GPT_SETTINGS, temp_settings_file, indent=4) async def chat_with_assistant(message, assistant_name): logger = logging.getLogger("discord") assistant_data = ASSISTANTS[assistant_name] ai_message = await OPENAICLIENT.beta.threads.messages.create( thread_id=assistant_data[3].id, role="user", content=message.content ) logger.info(ai_message) run = await OPENAICLIENT.beta.threads.runs.create_and_poll( thread_id=assistant_data[3].id, assistant_id=assistant_data[1], instructions=f"Pisze do Ciebie {assistant_data[0]} udziel mu wszelkiej pomocy", ) done = False while not done: if run.status == "completed": messsages = await OPENAICLIENT.beta.threads.messages.list( thread_id=assistant_data[3].id ) logger.info(messsages) reply_content = messsages.data[0].content logger.info(reply_content) chat_response = "" for block in reply_content: logger.info(block.text.value) chat_response += block.text.value await discord_friendly_send(message.channel, chat_response) # await message.channel.send(chat_response) done = True elif run.status == "cancelled": await discord_friendly_send(message.channel, "Cos sie wywaliło") else: logger.info(run.status) asyncio.sleep(5) async def echo(message): await discord_friendly_send(message.channel, f"Echo: {message.content}")