zebons / utils /llm.py
Noomes0's picture
init project
29505df verified
Raw
History Blame Contribute Delete
7.78 kB
# llm.py (адаптировано под LangChain >= 1.0)
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)