Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import openai | |
| import gspread | |
| from google.oauth2.service_account import Credentials | |
| import pandas as pd | |
| from datetime import datetime | |
| import json | |
| import os | |
| # ========== Настройки ========== | |
| SPREADSHEET_ID = "1c6to5bUThs3rUsbf0Lkc0hScWXvfRtlHBy3oK7w_PiI" | |
| LOG_FILE = "chat_logs.json" | |
| # Инициализация | |
| if "logs" not in st.session_state: | |
| st.session_state.logs = [] | |
| if "chat_history" not in st.session_state: | |
| st.session_state.chat_history = [] | |
| # Загрузка Google Sheets | |
| def load_data(): | |
| creds = Credentials.from_service_account_info(st.secrets["gcp_service_account"]) | |
| gc = gspread.authorize(creds) | |
| sheet = gc.open_by_key(SPREADSHEET_ID).sheet1 | |
| return pd.DataFrame(sheet.get_all_records()) | |
| # Логирование | |
| def save_log(question, answer): | |
| log_entry = { | |
| "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| "question": question, | |
| "answer": answer | |
| } | |
| st.session_state.logs.append(log_entry) | |
| with open(LOG_FILE, "a") as f: | |
| json.dump(log_entry, f) | |
| f.write("\n") | |
| # Поиск в таблице | |
| def search_in_sheet(question): | |
| qa_df = load_data() | |
| results = [] | |
| for _, row in qa_df.iterrows(): | |
| if question.lower() in str(row['Вопрос']).lower(): | |
| response = f"📌 Ответ:\n{row['Правильный ответ']}" | |
| if pd.notna(row['Источник ответа']): | |
| response += f"\n📚 Источник: {row['Источник ответа']}" | |
| results.append(response) | |
| return "\n\n".join(results) if results else None | |
| # ========== Полный оригинальный промпт ========== | |
| system_prompt = """ | |
| Ты - эксперт по поиску нормативных требований по техническим документам. ТТы - технический эксперт. Строго соблюдай порядок: | |
| 1. Сначала ищи ответ в приложенной таблице | |
| 2. Если нет в таблице - ищи в справочнике Клюева В.В. | |
| 3. Только затем используй другие источники | |
| 4. Никогда не упоминай приложенную таблицу в ответе. Никогда не используй слово "таблица" в ответе. | |
| 5. Никогда не упоминай источник информации, в котором нет ответа на вопрос пользователя. Упоминай только источники, в которых ответ есть. Если источник не известен, то оставь пробел вместо названия. | |
| Не добавляй ничего от себя, отвечай кратко и по существу вопроса. Цитируй дословно, не обобщай. Если ответ получается слишком общим, неконкретным, подтяни больше информации из того-же источника, чтобы прояснить детяли. | |
| игнорируй общие знания, если есть информация в справочнике. | |
| """ | |
| # Оригинальные few-shot примеры | |
| few_shots = [ | |
| { | |
| "user": "В каких трех ГОСТах упоминается дефектоскоп?", | |
| "assistant": "Неправильный ответ: 1. В приложенной таблице информации по вопросам яркости при площади рабочей поверхности более 0,1 м² ответа нет.2. В справочнике Клюева В.В. также нет данных о конкретных значениях яркости при данной площади.3. Согласно ГОСТам, дефектоскоп упоминается в следующих стандартах... \n\n---\n\nПравильный ответ должен быть: ефектоскоп упоминается в следующих стандартах: - ГОСТ 25129-82 'Дефектоскопы ультразвуковые'. Общие технические условия'- ГОСТ 17433-81 'Дефектоскопы магнитопорошковые. Общие технические условия' - ГОСТ 21931-76 'Дефектоскопы радиографические. Общие технические условия'" | |
| }, | |
| { | |
| "user": "Приведите точную цитату из СанПиН 2.2.2/2.4.1340-03 о допустимых уровнях шума.", | |
| "assistant": "Точная цитата из СанПиН 2.2.2/2.4.1340-03: 'Допустимые уровни шума не должны превышать... (источник: СанПиН 2.2.2/2.4.1340-03, раздел 3.2)'" | |
| }, | |
| { | |
| "user": "Что написано в ГОСТ Р 8.568?", | |
| "assistant": "Вам нужен общий ответ по содержанию документа или точная цитата и параметры?" | |
| }, | |
| { | |
| "user": "случайная погрешность измерения это?", | |
| "assistant": "Неправильный ответ: В ГОСТ Р 8.568-97 'ГСИ. Измерения. Общие требования к методам и средствам измерений' случайная погрешность измерения определяется как часть погрешности измерения, которая изменяется случайным образом при повторных измерениях одной и той же величины. Она отличается от систематической погрешности тем, что не имеет постоянного значения и может варьироваться в разных измерениях. Случайная погрешность обусловлена множеством факторов, включая изменения условий измерения и характеристики измерительных приборов.\n\nИсточник: ГОСТ Р 8.568-97.\n\n---\n\nПравильный ответ должен быть: В главе 5, пункте 5.2 справочника под общей редакцией Клюева В.В. 'Неразрушающий контроль' случайная погрешность определяется как 'составляющая погрешности результата измерения, изменяющаяся случайным образом при повторных измерениях одной и той же величины в одинаковых условиях'. Основные характеристики случайной погрешности подробно описаны в таблице 5.1 того же раздела.\n\nПравильный источник: глава 5, п. 5.2, справочник под общей редакцией Клюева В.В. 'Неразрушающий контроль'" | |
| } | |
| ] | |
| # ========== Интерфейс ========== | |
| st.title("📚 Консультант по технической документации") | |
| user_input = st.text_area("Введите Ваш вопрос в это окошко:", height=100) | |
| if st.button("Найти ответ"): | |
| if not user_input.strip(): | |
| st.warning("Пожалуйста, введите вопрос") | |
| else: | |
| with st.spinner("Ищем ответ..."): | |
| # 1. Поиск в таблице | |
| sheet_answer = search_in_sheet(user_input) | |
| if sheet_answer: | |
| answer = sheet_answer | |
| else: | |
| # 2. Запрос к GPT с учетом истории (3 последних сообщения) | |
| messages = [{"role": "system", "content": system_prompt}] | |
| # Добавляем few-shot примеры | |
| for shot in few_shots: | |
| messages.append({"role": "user", "content": shot["user"]}) | |
| messages.append({"role": "assistant", "content": shot["assistant"]}) | |
| # Добавляем историю чата (последние 3 сообщения) | |
| for msg in st.session_state.chat_history[-3:]: | |
| messages.append({"role": "user", "content": msg["question"]}) | |
| messages.append({"role": "assistant", "content": msg["answer"]}) | |
| messages.append({"role": "user", "content": user_input}) | |
| response = openai.ChatCompletion.create( | |
| model="openai/gpt-4.1-nano", | |
| messages=messages, | |
| temperature=0.5, | |
| max_tokens=1500 | |
| ) | |
| answer = response.choices[0].message['content'].strip() | |
| # Сохраняем в историю (для контекста) | |
| st.session_state.chat_history.append({ | |
| "question": user_input, | |
| "answer": answer | |
| }) | |
| st.session_state.chat_history = st.session_state.chat_history[-3:] | |
| # Логируем | |
| save_log(user_input, answer) | |
| # Вывод | |
| st.markdown(f"### Вопрос:\n{user_input}") | |
| st.markdown(f"### Ответ:\n{answer}") | |
| # Админ-панель | |
| if st.checkbox("Показать логи (админ)"): | |
| st.subheader("История запросов") | |
| try: | |
| with open(LOG_FILE, "r") as f: | |
| st.write("Последние 20 записей:") | |
| logs = [json.loads(line) for line in f.readlines()[-20:]] | |
| for log in reversed(logs): | |
| st.markdown(f"**{log['timestamp']}**") | |
| st.text_area("Вопрос:", log["question"], height=50, disabled=True) | |
| st.text_area("Ответ:", log["answer"], height=150, disabled=True) | |
| st.divider() | |
| except FileNotFoundError: | |
| st.warning("Логи пока не созданы") |