Spaces:
Runtime error
Runtime error
Update agent.py
Browse files
agent.py
CHANGED
@@ -9,7 +9,10 @@ from langgraph.graph import StateGraph, END
|
|
9 |
from typing import Dict, Any
|
10 |
from docx import Document
|
11 |
from pptx import Presentation
|
12 |
-
from langchain_ollama import ChatOllama
|
|
|
|
|
|
|
13 |
import logging
|
14 |
import importlib.util
|
15 |
import re
|
@@ -23,7 +26,7 @@ import torch
|
|
23 |
from faster_whisper import WhisperModel
|
24 |
from sentence_transformers import SentenceTransformer
|
25 |
import faiss
|
26 |
-
|
27 |
import asyncio
|
28 |
#from shazamio import Shazam
|
29 |
from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
|
@@ -31,7 +34,7 @@ from bs4 import BeautifulSoup
|
|
31 |
from typing import TypedDict, Optional
|
32 |
from faiss import IndexFlatL2
|
33 |
import pdfplumber
|
34 |
-
|
35 |
from retrying import retry
|
36 |
|
37 |
# Настройка путей для Hugging Face Spaces
|
@@ -122,11 +125,11 @@ def check_faiss():
|
|
122 |
raise ImportError("faiss не установлена. Установите: pip install faiss-cpu")
|
123 |
logger.info("faiss доступна.")
|
124 |
|
125 |
-
def check_ollama():
|
126 |
-
if importlib.util.find_spec("ollama") is None:
|
127 |
-
logger.error("ollama не установлена. Установите: pip install ollama")
|
128 |
-
raise ImportError("ollama не установлена. Установите: pip install ollama")
|
129 |
-
logger.info("ollama доступна.")
|
130 |
|
131 |
def check_shazamio():
|
132 |
if importlib.util.find_spec("shazamio") is None:
|
@@ -143,18 +146,67 @@ def check_langchain_community():
|
|
143 |
|
144 |
|
145 |
# Инициализация модели
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
try:
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
152 |
except Exception as e:
|
153 |
-
logger.error(f"Ошибка инициализации
|
154 |
raise e
|
155 |
|
156 |
|
157 |
|
|
|
|
|
158 |
|
159 |
# --- Состояние для LangGraph ---
|
160 |
class AgentState(TypedDict):
|
@@ -421,7 +473,7 @@ async def process_file(file_path: str, question: str) -> str:
|
|
421 |
check_faster_whisper()
|
422 |
check_sentence_transformers()
|
423 |
check_faiss()
|
424 |
-
check_ollama()
|
425 |
transcribed_text = transcribe_audio(file_path)
|
426 |
if transcribed_text.startswith("Error"):
|
427 |
logger.error(f"Ошибка транскрипции: {transcribed_text}")
|
@@ -844,7 +896,7 @@ def create_answer(state: AgentState) -> AgentState:
|
|
844 |
file_content = state["file_content"]
|
845 |
wiki_results = state["wiki_results"]
|
846 |
arxiv_results = state["arxiv_results"]
|
847 |
-
web_results = state.get("web_results", None)
|
848 |
except Exception as e:
|
849 |
logger.error(f"Ошибка извлечения ключей: {str(e)}")
|
850 |
return {"answer": f"Error extracting keys: {str(e)}", "raw_answer": f"Error extracting keys: {str(e)}"}
|
@@ -890,16 +942,15 @@ def create_answer(state: AgentState) -> AgentState:
|
|
890 |
if "card game" in question_lower:
|
891 |
logger.info("Обработка карточной игры...")
|
892 |
cards = ["2 of clubs", "3 of hearts", "King of spades", "Queen of hearts", "Jack of clubs", "Ace of diamonds"]
|
893 |
-
|
894 |
-
cards = cards[
|
895 |
-
cards = [cards[
|
896 |
-
cards = [cards[
|
897 |
-
cards = [cards[
|
898 |
-
cards =
|
899 |
-
cards = cards[
|
900 |
-
cards =
|
901 |
-
cards = cards[
|
902 |
-
cards = [cards[-1]] + cards[:-1] # 9. Нижняя наверх
|
903 |
state["answer"] = cards[0]
|
904 |
state["raw_answer"] = cards[0]
|
905 |
logger.info(f"Карточная игра обработана: {state['answer']}")
|
@@ -940,52 +991,20 @@ def create_answer(state: AgentState) -> AgentState:
|
|
940 |
state["raw_answer"] = f"Error: {e}"
|
941 |
return state
|
942 |
|
943 |
-
|
944 |
# Обработка MP3-файлов
|
945 |
file_path = state.get("file_path")
|
946 |
-
|
947 |
-
|
948 |
if file_path and file_path.endswith(".mp3"):
|
949 |
logger.info("Обработка MP3-файла")
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
|
|
954 |
|
955 |
-
#if "name of the song" in question_lower or "what song" in question_lower:
|
956 |
-
# logger.info("Распознавание песни")
|
957 |
-
# try:
|
958 |
-
# # Поскольку file_content уже содержит результат process_file
|
959 |
-
# if file_content and not file_content.startswith("Error"):
|
960 |
-
# state["answer"] = file_content if file_content != "Not found" else "Unknown"
|
961 |
-
# state["raw_answer"] = file_content
|
962 |
-
# logger.info(f"Ответ для песни: {state['answer']}")
|
963 |
-
# else:
|
964 |
-
# state["answer"] = "Unknown"
|
965 |
-
# state["raw_answer"] = "Error: No valid song recognition result"
|
966 |
-
# logger.error("Ошибка: результат распознавания песни недоступен")
|
967 |
-
# return state
|
968 |
-
# except Exception as e:
|
969 |
-
# logger.error(f"Ошибка распознавания песни: {str(e)}")
|
970 |
-
# state["answer"] = "Unknown"
|
971 |
-
# state["raw_answer"] = f"Error recognizing song: {str(e)}"
|
972 |
-
# return state
|
973 |
-
|
974 |
-
|
975 |
if "how long" in question_lower and "minute" in question_lower:
|
976 |
logger.info("Определение длительности аудио")
|
977 |
try:
|
978 |
-
# audio_path = os.path.join(DATA_DIR, "test", file_path) if Path(
|
979 |
-
# os.path.join(DATA_DIR, "test", file_path)).exists() else os.path.join(
|
980 |
-
# DATA_DIR, "validation", file_path)
|
981 |
-
# if not Path(audio_path).exists():
|
982 |
-
# logger.error(f"Аудиофайл не найден: {audio_path}")
|
983 |
-
# state["answer"] = "Unknown"
|
984 |
-
# state["raw_answer"] = "Error: Audio file not found"
|
985 |
-
# return state
|
986 |
-
# audio = pydub.AudioSegment.from_file(audio_path)
|
987 |
audio = pydub.AudioSegment.from_file(file_path)
|
988 |
-
|
989 |
duration_seconds = len(audio) / 1000
|
990 |
duration_minutes = round(duration_seconds / 60)
|
991 |
state["answer"] = str(duration_minutes)
|
@@ -997,6 +1016,7 @@ def create_answer(state: AgentState) -> AgentState:
|
|
997 |
state["answer"] = "Unknown"
|
998 |
state["raw_answer"] = f"Error: {e}"
|
999 |
return state
|
|
|
1000 |
# RAG для MP3 (аудиокниги)
|
1001 |
logger.info("RAG-обработка для MP3 (аудиокниги)")
|
1002 |
try:
|
@@ -1009,7 +1029,6 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1009 |
# Инициализация RAG
|
1010 |
check_sentence_transformers()
|
1011 |
check_faiss()
|
1012 |
-
check_ollama()
|
1013 |
rag_model = SentenceTransformer("all-MiniLM-L6-v2")
|
1014 |
index, sentences, embeddings = create_rag_index(file_content, rag_model)
|
1015 |
question_embedding = rag_model.encode([question], convert_to_numpy=True)
|
@@ -1024,7 +1043,7 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1024 |
|
1025 |
# Промпт для MP3 с RAG
|
1026 |
prompt = (
|
1027 |
-
"You are a highly precise assistant tasked with answering a question based solely on the provided context from an audiobook's transcribed text. "
|
1028 |
"Do not use any external knowledge or assumptions beyond the context. "
|
1029 |
"Extract the answer strictly from the context, ensuring it matches the question's requirements. "
|
1030 |
"If the question asks for an address, return only the street number and name (e.g., '123 Main'), excluding city, state, or street types (e.g., Street, Boulevard). "
|
@@ -1034,31 +1053,26 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1034 |
"Provide only the final answer, without explanations or additional text.\n"
|
1035 |
f"Question: {question}\n"
|
1036 |
f"Context: {relevant_context}\n"
|
1037 |
-
"Answer:"
|
1038 |
)
|
1039 |
logger.info(f"Промпт для RAG: {prompt[:200]}...")
|
1040 |
|
1041 |
-
# Вызов
|
1042 |
-
response =
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
"stop": ["\n"]
|
1050 |
-
}
|
1051 |
)
|
1052 |
-
answer = response
|
1053 |
-
logger.info(f"
|
1054 |
|
1055 |
# Проверка адресов
|
1056 |
if "address" in question_lower:
|
1057 |
-
# Удаляем типы улиц, город, штат
|
1058 |
answer = re.sub(r'\b(St\.|Street|Blvd\.|Boulevard|Ave\.|Avenue|Rd\.|Road|Dr\.|Drive)\b', '', answer, flags=re.IGNORECASE)
|
1059 |
-
# Удаляем город и штат (после запятых)
|
1060 |
answer = re.sub(r',\s*[^,]+$', '', answer).strip()
|
1061 |
-
# Убедимся, что остались только номер и имя улицы
|
1062 |
match = re.match(r'^\d+\s+[A-Za-z\s]+$', answer)
|
1063 |
if not match:
|
1064 |
logger.warning(f"Некорректный формат адреса: {answer}")
|
@@ -1074,19 +1088,16 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1074 |
state["raw_answer"] = f"Error RAG: {str(e)}"
|
1075 |
return state
|
1076 |
|
1077 |
-
|
1078 |
-
|
1079 |
-
|
1080 |
# Обработка вопросов с изображениями и Википедией
|
1081 |
logger.info("Проверка вопросов с изображениями и Википедией")
|
1082 |
if file_path and file_path.endswith((".jpg", ".png")) and "wikipedia" in question_lower:
|
1083 |
logger.info("Обработка изображения с Википедией")
|
1084 |
if wiki_results and not wiki_results.startswith("Error"):
|
1085 |
prompt = (
|
1086 |
-
f"Question: {question}\n"
|
1087 |
f"Wikipedia Content: {wiki_results[:1000]}\n"
|
1088 |
-
|
1089 |
-
"Answer:"
|
1090 |
)
|
1091 |
logger.info(f"Промпт для изображения с Википедией: {prompt[:200]}...")
|
1092 |
else:
|
@@ -1098,7 +1109,7 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1098 |
# Общий случай
|
1099 |
logger.info("Обработка общего случая")
|
1100 |
prompt = (
|
1101 |
-
f"Question: {question}\n"
|
1102 |
f"Instruction: Provide ONLY the final answer.\n"
|
1103 |
f"Examples:\n"
|
1104 |
f"- Number: '42'\n"
|
@@ -1114,7 +1125,7 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1114 |
prompt += f"Wikipedia Results: {wiki_results[:1000]}\n"
|
1115 |
has_context = True
|
1116 |
logger.info(f"Добавлен wiki_results: {wiki_results[:50]}...")
|
1117 |
-
if arxiv_results and not
|
1118 |
prompt += f"Arxiv Results: {arxiv_results[:1000]}\n"
|
1119 |
has_context = True
|
1120 |
logger.info(f"Добавлен arxiv_results: {arxiv_results[:50]}...")
|
@@ -1128,20 +1139,21 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1128 |
state["answer"] = "Unknown"
|
1129 |
state["raw_answer"] = "No context available"
|
1130 |
return state
|
1131 |
-
prompt += "Answer:"
|
1132 |
-
logger.info(f"
|
1133 |
|
1134 |
-
# Вызов
|
1135 |
-
logger.info("Вызов
|
1136 |
try:
|
1137 |
-
response =
|
1138 |
-
|
1139 |
-
|
1140 |
-
|
1141 |
-
|
1142 |
-
|
1143 |
-
|
1144 |
-
|
|
|
1145 |
state["raw_answer"] = raw_answer
|
1146 |
logger.info(f"Raw answer: {raw_answer[:100]}...")
|
1147 |
|
@@ -1151,59 +1163,24 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1151 |
clean_answer = re.sub(r'[^\w\s.-]', '', clean_answer)
|
1152 |
logger.info(f"Clean answer: {clean_answer[:100]}...")
|
1153 |
|
1154 |
-
|
1155 |
-
####################################################
|
1156 |
-
# Проверка на галлюцинации
|
1157 |
-
# def is_valid_answer(question, answer, context):
|
1158 |
-
# question_lower = question.lower()
|
1159 |
-
# if "address" in question_lower:
|
1160 |
-
# return bool(re.match(r'^\d+\s+[A-Za-z\s]+$', answer))
|
1161 |
-
# if "how many" in question_lower or "number" in question_lower:
|
1162 |
-
# return bool(re.match(r'^\d+(\.\d+)?$', answer))
|
1163 |
-
# if "format" in question_lower and "A.B.C.D." in question:
|
1164 |
-
# return bool(re.match(r'^[A-Z]\.[A-Z]\.[A-Z]\.[A-Z]\.', answer))
|
1165 |
-
# if context and answer.lower() not in context.lower():
|
1166 |
-
# return False
|
1167 |
-
# return True
|
1168 |
-
|
1169 |
-
# if not is_valid_answer(question, clean_answer, file_content or wiki_results or web_results):
|
1170 |
-
# logger.warning(f"Ответ не соответствует контексту: {clean_answer}")
|
1171 |
-
# state["answer"] = "Unknown"
|
1172 |
-
# state["raw_answer"] = "Invalid answer for context"
|
1173 |
-
# return state
|
1174 |
-
|
1175 |
-
# # Энтропийная проверка (опционально)
|
1176 |
-
# response = llm.invoke(prompt, return_logits=True)
|
1177 |
-
# if response.logits:
|
1178 |
-
# probs = np.exp(response.logits) / np.sum(np.exp(response.logits))
|
1179 |
-
# entropy = -np.sum(probs * np.log(probs + 1e-10))
|
1180 |
-
# if entropy > 2.0:
|
1181 |
-
# logger.warning(f"Высокая энтропия ответа: {entropy}")
|
1182 |
-
# state["answer"] = "Unknown"
|
1183 |
-
# state["raw_answer"] = "High uncertainty in response"
|
1184 |
-
# return state
|
1185 |
-
####################################################
|
1186 |
-
|
1187 |
-
|
1188 |
-
|
1189 |
if any(keyword in question_lower for keyword in ["how many", "number", "score", "difference", "citations"]):
|
1190 |
match = re.search(r"\d+(\.\d+)?", clean_answer)
|
1191 |
-
state["answer"] = match.group(0) if match else "
|
1192 |
elif "stock price" in question_lower:
|
1193 |
match = re.search(r"\d+\.\d+", clean_answer)
|
1194 |
-
state["answer"] = match.group(0) if match else "
|
1195 |
elif any(keyword in question_lower for keyword in ["name", "what is", "restaurant", "city", "replica", "line", "song"]):
|
1196 |
-
state["answer"] = clean_answer.split("\n")[0].strip() or "
|
1197 |
elif "address" in question_lower:
|
1198 |
match = re.search(r"\d+\s+[A-Za-z\s]+", clean_answer)
|
1199 |
-
state["answer"] = match.group(0) if match else "
|
1200 |
elif "The adventurer died" in clean_answer:
|
1201 |
state["answer"] = "The adventurer died."
|
1202 |
elif any(keyword in question_lower for keyword in ["code", "identifier", "issn"]):
|
1203 |
match = re.search(r"[\w-]+", clean_answer)
|
1204 |
-
state["answer"] = match.group(0) if match else "
|
1205 |
else:
|
1206 |
-
state["answer"] = clean_answer.split("\n")[0].strip() or "
|
1207 |
|
1208 |
logger.info(f"Final answer: {state['answer'][:50]}...")
|
1209 |
logger.info(f"Сгенерирован ответ: {state['answer'][:50]}...")
|
@@ -1214,7 +1191,6 @@ def create_answer(state: AgentState) -> AgentState:
|
|
1214 |
|
1215 |
return state
|
1216 |
|
1217 |
-
|
1218 |
|
1219 |
|
1220 |
# --- Создание графа ---
|
|
|
9 |
from typing import Dict, Any
|
10 |
from docx import Document
|
11 |
from pptx import Presentation
|
12 |
+
#from langchain_ollama import ChatOllama
|
13 |
+
#import ollama
|
14 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
|
15 |
+
|
16 |
import logging
|
17 |
import importlib.util
|
18 |
import re
|
|
|
26 |
from faster_whisper import WhisperModel
|
27 |
from sentence_transformers import SentenceTransformer
|
28 |
import faiss
|
29 |
+
|
30 |
import asyncio
|
31 |
#from shazamio import Shazam
|
32 |
from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
|
|
|
34 |
from typing import TypedDict, Optional
|
35 |
from faiss import IndexFlatL2
|
36 |
import pdfplumber
|
37 |
+
|
38 |
from retrying import retry
|
39 |
|
40 |
# Настройка путей для Hugging Face Spaces
|
|
|
125 |
raise ImportError("faiss не установлена. Установите: pip install faiss-cpu")
|
126 |
logger.info("faiss доступна.")
|
127 |
|
128 |
+
#def check_ollama():
|
129 |
+
# if importlib.util.find_spec("ollama") is None:
|
130 |
+
# logger.error("ollama не установлена. Установите: pip install ollama")
|
131 |
+
# raise ImportError("ollama не установлена. Установите: pip install ollama")
|
132 |
+
# logger.info("ollama доступна.")
|
133 |
|
134 |
def check_shazamio():
|
135 |
if importlib.util.find_spec("shazamio") is None:
|
|
|
146 |
|
147 |
|
148 |
# Инициализация модели
|
149 |
+
#try:
|
150 |
+
# llm = ChatOllama(base_url=OLLAMA_URL, model=MODEL_NAME, request_timeout=60)
|
151 |
+
# test_response = llm.invoke("Test")
|
152 |
+
# if test_response is None or not hasattr(test_response, 'content'):
|
153 |
+
# raise ValueError("Ollama модель недоступна или возвращает некорректный ответ")
|
154 |
+
# logger.info("Модель ChatOllama инициализирована.")
|
155 |
+
#except Exception as e:
|
156 |
+
# logger.error(f"Ошибка инициализации модели: {e}")
|
157 |
+
# raise e
|
158 |
+
|
159 |
try:
|
160 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
161 |
+
logger.info(f"Используемое устройство: {device}")
|
162 |
+
|
163 |
+
# Инициализация Qwen2-7B
|
164 |
+
qwen_tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2-7B-Instruct")
|
165 |
+
qwen_model = AutoModelForCausalLM.from_pretrained(
|
166 |
+
"Qwen/Qwen2-7B-Instruct",
|
167 |
+
device_map="auto",
|
168 |
+
load_in_4bit=True if device == "cuda" else False, # Квантование для GPU
|
169 |
+
torch_dtype=torch.float16 if device == "cuda" else torch.float32
|
170 |
+
)
|
171 |
+
qwen_pipeline = pipeline(
|
172 |
+
"text-generation",
|
173 |
+
model=qwen_model,
|
174 |
+
tokenizer=qwen_tokenizer,
|
175 |
+
device_map="auto"
|
176 |
+
)
|
177 |
+
logger.info("Модель Qwen2-7B-Instruct инициализирована.")
|
178 |
+
|
179 |
+
# Инициализация Mixtral-8x7B
|
180 |
+
mixtral_tokenizer = AutoTokenizer.from_pretrained("mistralai/Mixtral-8x7B-Instruct-v0.1")
|
181 |
+
mixtral_model = AutoModelForCausalLM.from_pretrained(
|
182 |
+
"mistralai/Mixtral-8x7B-Instruct-v0.1",
|
183 |
+
device_map="auto",
|
184 |
+
load_in_4bit=True if device == "cuda" else False,
|
185 |
+
torch_dtype=torch.float16 if device == "cuda" else torch.float32
|
186 |
+
)
|
187 |
+
mixtral_pipeline = pipeline(
|
188 |
+
"text-generation",
|
189 |
+
model=mixtral_model,
|
190 |
+
tokenizer=mixtral_tokenizer,
|
191 |
+
device_map="auto"
|
192 |
+
)
|
193 |
+
logger.info("Модель Mixtral-8x7B-Instruct инициализирована.")
|
194 |
+
|
195 |
+
# Тестовый вызов для Qwen
|
196 |
+
test_input = qwen_tokenizer("Test", return_tensors="pt").to(device)
|
197 |
+
test_output = qwen_model.generate(**test_input, max_new_tokens=10)
|
198 |
+
test_response = qwen_tokenizer.decode(test_output[0], skip_special_tokens=True)
|
199 |
+
if not test_response:
|
200 |
+
raise ValueError("Qwen2-7B модель недоступна или возвращает пустой ответ")
|
201 |
+
logger.info(f"Тестовый ответ Qwen2-7B: {test_response}")
|
202 |
except Exception as e:
|
203 |
+
logger.error(f"Ошибка инициализации моделей: {e}")
|
204 |
raise e
|
205 |
|
206 |
|
207 |
|
208 |
+
|
209 |
+
|
210 |
|
211 |
# --- Состояние для LangGraph ---
|
212 |
class AgentState(TypedDict):
|
|
|
473 |
check_faster_whisper()
|
474 |
check_sentence_transformers()
|
475 |
check_faiss()
|
476 |
+
#check_ollama()
|
477 |
transcribed_text = transcribe_audio(file_path)
|
478 |
if transcribed_text.startswith("Error"):
|
479 |
logger.error(f"Ошибка транскрипции: {transcribed_text}")
|
|
|
896 |
file_content = state["file_content"]
|
897 |
wiki_results = state["wiki_results"]
|
898 |
arxiv_results = state["arxiv_results"]
|
899 |
+
web_results = state.get("web_results", None)
|
900 |
except Exception as e:
|
901 |
logger.error(f"Ошибка извлечения ключей: {str(e)}")
|
902 |
return {"answer": f"Error extracting keys: {str(e)}", "raw_answer": f"Error extracting keys: {str(e)}"}
|
|
|
942 |
if "card game" in question_lower:
|
943 |
logger.info("Обработка карточной игры...")
|
944 |
cards = ["2 of clubs", "3 of hearts", "King of spades", "Queen of hearts", "Jack of clubs", "Ace of diamonds"]
|
945 |
+
cards = cards[3:] + cards[:3]
|
946 |
+
cards = [cards[1], cards[0]] + cards[2:]
|
947 |
+
cards = [cards[2]] + cards[:2] + cards[3:]
|
948 |
+
cards = [cards[-1]] + cards[:-1]
|
949 |
+
cards = [cards[2]] + cards[:2] + cards[3:]
|
950 |
+
cards = cards[4:] + cards[:4]
|
951 |
+
cards = [cards[-1]] + cards[:-1]
|
952 |
+
cards = cards[2:] + cards[:2]
|
953 |
+
cards = [cards[-1]] + cards[:-1]
|
|
|
954 |
state["answer"] = cards[0]
|
955 |
state["raw_answer"] = cards[0]
|
956 |
logger.info(f"Карточная игра обработана: {state['answer']}")
|
|
|
991 |
state["raw_answer"] = f"Error: {e}"
|
992 |
return state
|
993 |
|
|
|
994 |
# Обработка MP3-файлов
|
995 |
file_path = state.get("file_path")
|
|
|
|
|
996 |
if file_path and file_path.endswith(".mp3"):
|
997 |
logger.info("Обработка MP3-файла")
|
998 |
+
if "name of the song" in question_lower or "what song" in question_lower():
|
999 |
+
logger.warning("Распознавание песен больше не поддерживается: shazamio не установлена")
|
1000 |
+
state["answer"] = "Unknown"
|
1001 |
+
state["raw_answer"] = "Song recognition not supported"
|
1002 |
+
return state
|
1003 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1004 |
if "how long" in question_lower and "minute" in question_lower:
|
1005 |
logger.info("Определение длительности аудио")
|
1006 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1007 |
audio = pydub.AudioSegment.from_file(file_path)
|
|
|
1008 |
duration_seconds = len(audio) / 1000
|
1009 |
duration_minutes = round(duration_seconds / 60)
|
1010 |
state["answer"] = str(duration_minutes)
|
|
|
1016 |
state["answer"] = "Unknown"
|
1017 |
state["raw_answer"] = f"Error: {e}"
|
1018 |
return state
|
1019 |
+
|
1020 |
# RAG для MP3 (аудиокниги)
|
1021 |
logger.info("RAG-обработка для MP3 (аудиокниги)")
|
1022 |
try:
|
|
|
1029 |
# Инициализация RAG
|
1030 |
check_sentence_transformers()
|
1031 |
check_faiss()
|
|
|
1032 |
rag_model = SentenceTransformer("all-MiniLM-L6-v2")
|
1033 |
index, sentences, embeddings = create_rag_index(file_content, rag_model)
|
1034 |
question_embedding = rag_model.encode([question], convert_to_numpy=True)
|
|
|
1043 |
|
1044 |
# Промпт для MP3 с RAG
|
1045 |
prompt = (
|
1046 |
+
"[INST] You are a highly precise assistant tasked with answering a question based solely on the provided context from an audiobook's transcribed text. "
|
1047 |
"Do not use any external knowledge or assumptions beyond the context. "
|
1048 |
"Extract the answer strictly from the context, ensuring it matches the question's requirements. "
|
1049 |
"If the question asks for an address, return only the street number and name (e.g., '123 Main'), excluding city, state, or street types (e.g., Street, Boulevard). "
|
|
|
1053 |
"Provide only the final answer, without explanations or additional text.\n"
|
1054 |
f"Question: {question}\n"
|
1055 |
f"Context: {relevant_context}\n"
|
1056 |
+
"Answer: [/INST]"
|
1057 |
)
|
1058 |
logger.info(f"Промпт для RAG: {prompt[:200]}...")
|
1059 |
|
1060 |
+
# Вызов Mixtral-8x7B
|
1061 |
+
response = mixtral_pipeline(
|
1062 |
+
prompt,
|
1063 |
+
max_new_tokens=100,
|
1064 |
+
temperature=0.0,
|
1065 |
+
top_p=0.9,
|
1066 |
+
do_sample=False,
|
1067 |
+
return_full_text=False
|
|
|
|
|
1068 |
)
|
1069 |
+
answer = response[0]["generated_text"].strip() or "Not found"
|
1070 |
+
logger.info(f"Mixtral-8x7B вернул ответ: {answer}")
|
1071 |
|
1072 |
# Проверка адресов
|
1073 |
if "address" in question_lower:
|
|
|
1074 |
answer = re.sub(r'\b(St\.|Street|Blvd\.|Boulevard|Ave\.|Avenue|Rd\.|Road|Dr\.|Drive)\b', '', answer, flags=re.IGNORECASE)
|
|
|
1075 |
answer = re.sub(r',\s*[^,]+$', '', answer).strip()
|
|
|
1076 |
match = re.match(r'^\d+\s+[A-Za-z\s]+$', answer)
|
1077 |
if not match:
|
1078 |
logger.warning(f"Некорректный формат адреса: {answer}")
|
|
|
1088 |
state["raw_answer"] = f"Error RAG: {str(e)}"
|
1089 |
return state
|
1090 |
|
|
|
|
|
|
|
1091 |
# Обработка вопросов с изображениями и Википедией
|
1092 |
logger.info("Проверка вопросов с изображениями и Википедией")
|
1093 |
if file_path and file_path.endswith((".jpg", ".png")) and "wikipedia" in question_lower:
|
1094 |
logger.info("Обработка изображения с Википедией")
|
1095 |
if wiki_results and not wiki_results.startswith("Error"):
|
1096 |
prompt = (
|
1097 |
+
f"[INST] Question: {question}\n"
|
1098 |
f"Wikipedia Content: {wiki_results[:1000]}\n"
|
1099 |
+
"Instruction: Provide ONLY the final answer.\n"
|
1100 |
+
"Answer: [/INST]"
|
1101 |
)
|
1102 |
logger.info(f"Промпт для изображения с Википедией: {prompt[:200]}...")
|
1103 |
else:
|
|
|
1109 |
# Общий случай
|
1110 |
logger.info("Обработка общего случая")
|
1111 |
prompt = (
|
1112 |
+
f"[INST] Question: {question}\n"
|
1113 |
f"Instruction: Provide ONLY the final answer.\n"
|
1114 |
f"Examples:\n"
|
1115 |
f"- Number: '42'\n"
|
|
|
1125 |
prompt += f"Wikipedia Results: {wiki_results[:1000]}\n"
|
1126 |
has_context = True
|
1127 |
logger.info(f"Добавлен wiki_results: {wiki_results[:50]}...")
|
1128 |
+
if arxiv_results and not wiki_results.startswith("Error"):
|
1129 |
prompt += f"Arxiv Results: {arxiv_results[:1000]}\n"
|
1130 |
has_context = True
|
1131 |
logger.info(f"Добавлен arxiv_results: {arxiv_results[:50]}...")
|
|
|
1139 |
state["answer"] = "Unknown"
|
1140 |
state["raw_answer"] = "No context available"
|
1141 |
return state
|
1142 |
+
prompt += "Answer: [/INST]"
|
1143 |
+
logger.info(f"Промпт: {prompt[:200]}...")
|
1144 |
|
1145 |
+
# Вызов Qwen2-7B
|
1146 |
+
logger.info("Вызов Qwen2-7B")
|
1147 |
try:
|
1148 |
+
response = qwen_pipeline(
|
1149 |
+
prompt,
|
1150 |
+
max_new_tokens=100,
|
1151 |
+
temperature=0.0,
|
1152 |
+
top_p=0.9,
|
1153 |
+
do_sample=False,
|
1154 |
+
return_full_text=False
|
1155 |
+
)
|
1156 |
+
raw_answer = response[0]["generated_text"].strip() or "Unknown"
|
1157 |
state["raw_answer"] = raw_answer
|
1158 |
logger.info(f"Raw answer: {raw_answer[:100]}...")
|
1159 |
|
|
|
1163 |
clean_answer = re.sub(r'[^\w\s.-]', '', clean_answer)
|
1164 |
logger.info(f"Clean answer: {clean_answer[:100]}...")
|
1165 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1166 |
if any(keyword in question_lower for keyword in ["how many", "number", "score", "difference", "citations"]):
|
1167 |
match = re.search(r"\d+(\.\d+)?", clean_answer)
|
1168 |
+
state["answer"] = match.group(0) if match else "Not found"
|
1169 |
elif "stock price" in question_lower:
|
1170 |
match = re.search(r"\d+\.\d+", clean_answer)
|
1171 |
+
state["answer"] = match.group(0) if match else "Not found"
|
1172 |
elif any(keyword in question_lower for keyword in ["name", "what is", "restaurant", "city", "replica", "line", "song"]):
|
1173 |
+
state["answer"] = clean_answer.split("\n")[0].strip() or "Not found"
|
1174 |
elif "address" in question_lower:
|
1175 |
match = re.search(r"\d+\s+[A-Za-z\s]+", clean_answer)
|
1176 |
+
state["answer"] = match.group(0) if match else "Not found"
|
1177 |
elif "The adventurer died" in clean_answer:
|
1178 |
state["answer"] = "The adventurer died."
|
1179 |
elif any(keyword in question_lower for keyword in ["code", "identifier", "issn"]):
|
1180 |
match = re.search(r"[\w-]+", clean_answer)
|
1181 |
+
state["answer"] = match.group(0) if match else "Not found"
|
1182 |
else:
|
1183 |
+
state["answer"] = clean_answer.split("\n")[0].strip() or "Not found"
|
1184 |
|
1185 |
logger.info(f"Final answer: {state['answer'][:50]}...")
|
1186 |
logger.info(f"Сгенерирован ответ: {state['answer'][:50]}...")
|
|
|
1191 |
|
1192 |
return state
|
1193 |
|
|
|
1194 |
|
1195 |
|
1196 |
# --- Создание графа ---
|