|
|
| import os
|
| import json
|
| from mistralai.client import Mistral
|
| from langchain_community.tools import TavilySearchResults, JinaSearch
|
| from langchain_text_splitters import CharacterTextSplitter
|
| from langchain_mistralai import ChatMistralAI
|
| from langchain_core.prompts import PromptTemplate
|
| from langchain_core.documents import Document
|
| from transformers import AutoTokenizer
|
| from utils.search_article import *
|
| import concurrent.futures
|
|
|
| tokenizer = AutoTokenizer.from_pretrained("mistral-community/pixtral-12b")
|
|
|
| if not os.environ.get("TAVILY_API_KEY"):
|
| os.environ["TAVILY_API_KEY"] = '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'
|
|
|
| api_key_2 = os.getenv("MISTRAL_API_KEY_2")
|
| client_2 = Mistral(api_key=api_key_2)
|
|
|
| api_key_3 = os.getenv("MISTRAL_API_KEY_3")
|
| client_3 = Mistral(api_key=api_key_3)
|
|
|
| api_key_4 = os.getenv("MISTRAL_API_KEY_4")
|
| client_4 = ChatMistralAI(api_key=api_key_4, model="pixtral-12b-2409")
|
|
|
|
|
| def count_tokens_in_text(text):
|
| tokens = tokenizer(text, return_tensors="pt", truncation=False, add_special_tokens=True)
|
| return len(tokens["input_ids"][0])
|
|
|
|
|
| def setup_search(question):
|
| try:
|
| tavily_tool = TavilySearchResults(max_results=20)
|
| results = tavily_tool.invoke({"query": f"{question}"})
|
| if isinstance(results, list):
|
| return results, 'tavily_tool'
|
| else:
|
| print("Unexpected format from TavilySearchResults:", results)
|
| except Exception as e:
|
| print("Error with TavilySearchResults:", e)
|
|
|
| try:
|
| jina_tool = JinaSearch()
|
| results = json.loads(str(jina_tool.invoke({"query": f"{question}"})))
|
| if isinstance(results, list):
|
| return results, 'jina_tool'
|
| else:
|
| print("Unexpected format from JinaSearch:", results)
|
| except Exception as e:
|
| print("Error with JinaSearch:", e)
|
|
|
| return [], ''
|
|
|
|
|
| def ask_question_to_mistral(text, question, context, images=[]):
|
| prompt = f"Answer the following question without mentioning it or repeating the original text on which the question is asked in style markdown. IN RUSSIAN:\nQuestion: {question}\n\nText:\n{text}"
|
| message_content = [{"type": "text", "text": prompt}] + images
|
|
|
| response = client_2.chat.complete(
|
| model="pixtral-12b-2409",
|
| messages=[{"role": "user", "content": f'{message_content}\n\nAdditional Context from Web Search:\n{context}'}],
|
| )
|
| return response.choices[0].message.content
|
|
|
|
|
| def process_article_for_summary(text, preferences, images=[], compression_percentage=30):
|
| prompt = f"""
|
| You are a commentator.
|
| # article:
|
| {text}
|
|
|
| # Instructions:
|
| ## Summarize:
|
| In clear and concise language, summarize the key points and themes presented in the article by cutting it by {compression_percentage} percent in the markdown format.
|
| Taking into account the user's wishes "{preferences}"
|
| """
|
| message_content = [{"type": "text", "text": prompt}] + images
|
| response = client_3.chat.complete(
|
| model="pixtral-12b-2409",
|
| messages=[{"role": "user", "content": message_content}]
|
| ).choices[0].message.content
|
| return response
|
|
|
|
|
| def process_large_article_for_summary(text, preferences, images=[], compression_percentage=30):
|
| text_splitter = CharacterTextSplitter.from_huggingface_tokenizer(
|
| tokenizer, chunk_size=100000, chunk_overlap=14000
|
| )
|
| docs = text_splitter.create_documents([text])
|
|
|
| map_template = f"""На основе приведенного текста, выполните сжатие, выделяя основные темы и важные моменты.
|
| Уровень сжатия: {compression_percentage}%.
|
| С учетом пожеланий пользователя "{preferences}"
|
| Текст: {{text}}
|
| Полезный ответ:"""
|
|
|
| map_prompt = PromptTemplate.from_template(map_template)
|
| summaries = []
|
| for doc in docs:
|
| formatted_prompt = map_prompt.format(text=doc.page_content)
|
| response = client_4.invoke(formatted_prompt)
|
| summaries.append(response.content)
|
|
|
| combined_summaries = "\n\n".join(summaries)
|
| reduce_template = f"""Следующий текст состоит из нескольких кратких итогов:
|
| {{text}}
|
|
|
| На основе этих кратких итогов, выполните финальное сжатие текста, объединяя основные темы и ключевые моменты.
|
| Уровень сжатия: {compression_percentage}%.
|
| С учетом пожеланий пользователя "{preferences}"
|
| Результат предоставьте на русском языке в формате Markdown.
|
|
|
| Полезный ответ:"""
|
|
|
| reduce_prompt = PromptTemplate.from_template(reduce_template)
|
| final_response = client_4.invoke(reduce_prompt.format(text=combined_summaries))
|
| return final_response.content
|
|
|
|
|
| def ask_question_to_mistral_with_large_text(text, question, context, images=[]):
|
| text_splitter = CharacterTextSplitter.from_huggingface_tokenizer(
|
| tokenizer, chunk_size=100000, chunk_overlap=14000
|
| )
|
| docs = text_splitter.create_documents([text])
|
|
|
| map_template = f"""На основе текста ответьте на вопрос. Ответ должен быть на русском языке.
|
| Вопрос: {question}
|
| Текст: {{text}}
|
| Информация из интернета: {context}
|
| Полезный ответ:"""
|
|
|
| map_prompt = PromptTemplate.from_template(map_template)
|
| partial_answers = []
|
| for doc in docs:
|
| formatted_prompt = map_prompt.format(text=doc.page_content)
|
| response = client_4.invoke(formatted_prompt)
|
| partial_answers.append(response.content)
|
|
|
| combined_answers = "\n\n".join(partial_answers)
|
| reduce_template = """Следующий текст содержит несколько кратких ответов на вопрос:
|
| {{text}}
|
|
|
| Объедините их в финальный ответ. Ответ предоставьте на русском языке в формате Markdown.
|
|
|
| Полезный ответ:"""
|
|
|
| reduce_prompt = PromptTemplate.from_template(reduce_template)
|
| final_response = client_4.invoke(reduce_prompt.format(text=combined_answers))
|
| return final_response.content
|
|
|
|
|
| def init_summ(text, preferences, images=[], compression_percentage=30):
|
| if len(images) >= 8:
|
| images = images[:7]
|
|
|
| if count_tokens_in_text(text=text) < 128_000:
|
| return process_article_for_summary(text, preferences, images, compression_percentage)
|
| else:
|
| return process_large_article_for_summary(text, preferences, images, compression_percentage)
|
|
|
|
|
| def init_qa(text, question, images=[]):
|
| if len(images) >= 8:
|
| images = images[:7]
|
|
|
| search_tool, tool = setup_search(question)
|
| context = ''
|
| if search_tool:
|
| if tool == 'tavily_tool':
|
| for result in search_tool:
|
| context += f"{result.get('url', 'N/A')} : {result.get('content', 'No content')} \n"
|
| elif tool == 'jina_tool':
|
| for result in search_tool:
|
| context += f"{result.get('link', 'N/A')} : {result.get('snippet', 'No snippet')} : {result.get('content', 'No content')} \n"
|
|
|
| if count_tokens_in_text(text=(text + context)) < 128_000:
|
| return ask_question_to_mistral(text, question, context, images)
|
| else:
|
| return ask_question_to_mistral_with_large_text(text, question, context, images)
|
|
|