diff --git a/.DS_Store b/.DS_Store index d883f304cabe42ed92225f23e1856528407e5c08..c2fa7e35740f3d8f3f920b5c959a03cba577c64b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/services/b2b/b2b_service.py b/.history/src/b2b/b2b_service_20250630021334.py similarity index 100% rename from services/b2b/b2b_service.py rename to .history/src/b2b/b2b_service_20250630021334.py diff --git a/.history/src/b2b/b2b_service_20250630135358.py b/.history/src/b2b/b2b_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..4e141d76a73045a52dd19e92bdf6c63196acdc67 --- /dev/null +++ b/.history/src/b2b/b2b_service_20250630135358.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.b2b.b2b_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class B2BService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/base_graph.py b/.history/src/base_graph_20250629185111.py similarity index 100% rename from services/base_graph.py rename to .history/src/base_graph_20250629185111.py diff --git a/.history/src/base_graph_20250630135358.py b/.history/src/base_graph_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..c391573bed5f45b83c459a54f1eee0c91777ca94 --- /dev/null +++ b/.history/src/base_graph_20250630135358.py @@ -0,0 +1,126 @@ +from typing import Annotated, Type +from langgraph.graph import StateGraph +from langchain_core.messages import HumanMessage, ToolMessage +from langgraph.graph.message import add_messages +from typing_extensions import TypedDict +from src.base_service import BaseService +from src.get_answer_gigachat import AnswerGigaChat +import logging +import os + +logger = logging.getLogger(__name__) + +class State(TypedDict): + messages: Annotated[list, add_messages] + +class BaseGraph: + def __init__(self, service: Type[BaseService]): + self.service = service + # self._initialize_logging() + self.tools_dict = {tool.name: tool for tool in service.tools} + self.llm_with_tools = AnswerGigaChat().bind_tools(service.tools) + self.messages = service.get_initial_messages() + self.graph = self._build_graph() + logger.info(f"BaseGraph with service {service} was built") + + def rebuild_with_new_service(self, service: Type[BaseService]): + self.service = service + self.tools_dict = {tool.name: tool for tool in service.tools} + self.llm_with_tools = AnswerGigaChat().bind_tools(service.tools) + self.graph = self._build_graph() + self.messages = service.get_messages_from_redirect(self.messages) + logger.info(f"BaseGraph was rebuilt with service {service}") + + def _initialize_logging(self): + if os.path.exists(self.service.log_path): + os.remove(self.service.log_path) + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.FileHandler(self.service.log_path)] + ) + + def _agent_node(self, state: State): + try: + logger.info("Starting agent_node") + messages = state["messages"] + response = self.llm_with_tools.invoke(messages) + response.content = self._clean_response(response.content) + return {"messages": [response]} + except Exception as e: + logger.error(f"Error in agent_node: {str(e)}", exc_info=True) + raise + + def _tool_node(self, state: State): + try: + logger.info("Starting tool_node") + last_message = state["messages"][-1] + tool_calls = last_message.tool_calls + + results = [] + for call in tool_calls: + tool_name = call["name"] + logger.info(f"Running tool {tool_name}") + args = call["args"] + + tool = self.tools_dict.get(tool_name) + if not tool: + raise ValueError(f"Tool {tool_name} not found") + + tool_response = tool.invoke(args) + if tool_name == "make_redirect": + self.rebuild_with_new_service(tool_response) + results.append(str(tool_response)) + + return {"messages": [ToolMessage(content=", ".join(results), tool_call_id=call["id"])]} + except Exception as e: + logger.error(f"Error in tool_node: {str(e)}", exc_info=True) + raise + + def _should_continue(self, state: State): + try: + logger.info("Checking should continue") + last_message = state["messages"][-1] + return "tool" if "function_call" in last_message.additional_kwargs else "end" + except Exception as e: + logger.error(f"Error in should_continue: {str(e)}", exc_info=True) + raise + + def _build_graph(self): + try: + logger.info("Building graph") + graph_builder = StateGraph(State) + + graph_builder.add_node("agent", self._agent_node) + graph_builder.add_node("tool", self._tool_node) + + graph_builder.add_conditional_edges( + "agent", + self._should_continue, + {"tool": "tool", "end": "__end__"} + ) + graph_builder.add_edge("tool", "agent") + graph_builder.set_entry_point("agent") + + return graph_builder.compile() + except Exception as e: + logger.error(f"Error building graph: {str(e)}", exc_info=True) + raise + + def _clean_response(self, content: str) -> str: + content = content.replace("", "").replace("", "") + if "" in content: + content = content.split("")[1] + if "" in content: + content = content.split("")[-1] + return content + + def invoke(self, user_input): + try: + self.messages.append(HumanMessage(content=user_input)) + result = self.graph.invoke({"messages": self.messages}) + self.messages = result["messages"] + return result + except Exception as e: + logger.error(f"Error invoking graph: {str(e)}", exc_info=True) + raise diff --git a/services/base_service.py b/.history/src/base_service_20250630134728.py similarity index 100% rename from services/base_service.py rename to .history/src/base_service_20250630134728.py diff --git a/.history/src/base_service_20250630135358.py b/.history/src/base_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..dea09b6af9bc847bfee045f0da90364fd724f527 --- /dev/null +++ b/.history/src/base_service_20250630135358.py @@ -0,0 +1,76 @@ +from abc import ABC, abstractmethod +from langchain_core.messages import SystemMessage, HumanMessage, AIMessage +from system_prompt_template import DIALOG_PROMPT +from typing import List +from langchain_core.tools import BaseTool, tool +from src.service_by_name import get_service_by_name +import logging +import time +import os +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +class BaseService(ABC): + def __init__(self): + self._base_tools = [self.closing_task, + self.make_jira_task, + ] + + @property + @abstractmethod + def system_prompt(self) -> str: + """Возвращает системный промпт для сервиса""" + pass + + @property + @abstractmethod + def tools(self) -> List[BaseTool]: + """Возвращает список инструментов сервиса""" + pass + + @property + def log_path(self) -> str: + """Возвращает путь до папки с логами""" + return os.getenv("LOG_SERVICES_PATH") + + def get_initial_messages(self): + """Возвращает начальные сообщения для графа""" + return [SystemMessage(content=self.system_prompt)] + + def get_messages_from_redirect(self, messages): + messages_formatted = "" + for message in messages: + if isinstance(message, HumanMessage): + message += f"user: '{message.content}'\n" + elif isinstance(message, AIMessage): + message += f"assistant: '{message.content}'\n" + + return [SystemMessage(content=self.system_prompt + DIALOG_PROMPT.format(messages_formatted))] + + @tool + def make_redirect(service_name: str): + """Когда оказывается, что пользователю нужен эксперт в другом сервисе, + пользователя нужно перенаправить на соответствующего эксперта. + Функция принимает аргумент service_name из списка: + ['openvpn', 'corporate_email', 'internet_access'] и пересоздает класс эксперта + с соответствующими новому сервису знаниями.""" + logger.info("redirecting question to service {service_name}") + return get_service_by_name(service_name) + + + @tool + def closing_task(question: str) -> None: + """Задача выполнена, сохраняем результаты""" + print("### Задача выполнена, сохраняем результаты") + time.sleep(2) + logger.info("task closed") + + @tool + def make_jira_task(question: str) -> None: + """Задача не может быть выполнена с помощью ии, создаем задачу в jira для того, + чтобы её выполнил человек""" + time.sleep(2) + print("### Скриптом создается задача в jira с соответствующими деталями") + logger.info("jira task was make") diff --git a/services/borlas/borlas_service.py b/.history/src/borlas/borlas_service_20250630021744.py similarity index 100% rename from services/borlas/borlas_service.py rename to .history/src/borlas/borlas_service_20250630021744.py diff --git a/.history/src/borlas/borlas_service_20250630135358.py b/.history/src/borlas/borlas_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..d1bae7c3779ada7c769b4d722b2a71dea2dfb9ff --- /dev/null +++ b/.history/src/borlas/borlas_service_20250630135358.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.borlas.borlas_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class BorlasService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/corporate_email/corporate_email_service.py b/.history/src/corporate_email/corporate_email_service_20250628144053.py similarity index 100% rename from services/corporate_email/corporate_email_service.py rename to .history/src/corporate_email/corporate_email_service_20250628144053.py diff --git a/.history/src/corporate_email/corporate_email_service_20250630135358.py b/.history/src/corporate_email/corporate_email_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..8b13b53638de53410472b11a7e3a614bd2a0b21e --- /dev/null +++ b/.history/src/corporate_email/corporate_email_service_20250630135358.py @@ -0,0 +1,24 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.corporate_email.corporate_email_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class CorporateEmailService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [] + + @tool + def check_lock_in_active_dir_and_password(question: str) -> None: + """Проверка блокировки учетной записи сотрудника в системе и разблокировка и проверка на просроченный пароль и обновление пароля. Если эти проблемы вскрываются, то функция их исправляет.""" + time.sleep(4) + logger.info("check_lock_in_active_dir_and_password") diff --git a/services/crm/crm_service.py b/.history/src/crm/crm_service_20250630021458.py similarity index 100% rename from services/crm/crm_service.py rename to .history/src/crm/crm_service_20250630021458.py diff --git a/.history/src/crm/crm_service_20250630135358.py b/.history/src/crm/crm_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..9518a955cebbf220551d8fb5eecf5995acbbd987 --- /dev/null +++ b/.history/src/crm/crm_service_20250630135358.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.crm.crm_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class CRMService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/internet_access/internet_access_service.py b/.history/src/internet_access/internet_access_service_20250630012853.py similarity index 100% rename from services/internet_access/internet_access_service.py rename to .history/src/internet_access/internet_access_service_20250630012853.py diff --git a/.history/src/internet_access/internet_access_service_20250630135358.py b/.history/src/internet_access/internet_access_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..317737aaf6244e1b74d320803afa0d6fc693b257 --- /dev/null +++ b/.history/src/internet_access/internet_access_service_20250630135358.py @@ -0,0 +1,27 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.internet_access.internet_access_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + +class InternetAccessService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/one_c/one_c_service.py b/.history/src/one_c/one_c_service_20250630021626.py similarity index 100% rename from services/one_c/one_c_service.py rename to .history/src/one_c/one_c_service_20250630021626.py diff --git a/.history/src/one_c/one_c_service_20250630135358.py b/.history/src/one_c/one_c_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..04aa455b82dec5865db1e1cc017e395a386a0a31 --- /dev/null +++ b/.history/src/one_c/one_c_service_20250630135358.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.one_c.one_c_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class OneCService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/openvpn/openvpn_prompts.py b/.history/src/openvpn/openvpn_prompts_20250627163312.py similarity index 100% rename from services/openvpn/openvpn_prompts.py rename to .history/src/openvpn/openvpn_prompts_20250627163312.py diff --git a/.history/src/openvpn/openvpn_prompts_20250630135358.py b/.history/src/openvpn/openvpn_prompts_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..d4fa9cafc1f8d2b25923be34985277b82e4d27b8 --- /dev/null +++ b/.history/src/openvpn/openvpn_prompts_20250630135358.py @@ -0,0 +1,103 @@ +from src.system_prompt_template import SYSTEM_PROMPT_TEMPLATE + +service_name = "open vpn" + +service_description = "" + +cases = """ +У пользователя может быть одна из 3 ситуаций, надо определить, какая у него: + +- Консультация. + Это когда: + - Программа есть + - Программа открывается + - vpn подключается + В этом случае надо предпринять действия: + - Проконсультировать пользователя, как пользоваться vpn. + и ответить на его вопросы. + - Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение. + в техническую поддержку + - Если ты не можешь помочь пользователю или он задает вопросы, на которые у тебя нет инструкций, + вызови функцию make_jira_task, сообщи, что была создана задача в jira и в ближайшее времся с + пользователем свяжется сотрудник технической поддержки. + +- Установка + Это когда: + - Программы нет на компьютере пользователя. + В этом случае надо предпринять действия: + - Вызывать функцию install_program + - Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение + в техническую поддержку + - Если пользователь говорит, что vpn по-прежнему не работает, вызови функцию script_part + +- Переустановка + Это когда: + - Программы не открывается. + В этом случае надо предпринять действия: + - Вызывать функцию delete_and_install_program + - Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение + в техническую поддержку + - Если пользователь говорит, что vpn по-прежнему не работает, вызови функцию script_part +""" + + +broken_cases = """ +'В таком случае вам необходимо установить программу OpenVPN. Для этого выполните следующие действия: + +Перейдите на официальный сайт OpenVPN (https://openvpn.net/) и скачайте последнюю версию программы. +Запустите установочный файл и следуйте инструкциям мастера установки. +После завершения установки запустите программу и попробуйте подключить VPN. +Если возникнут трудности при установке или использовании программы, пожалуйста, дайте мне знать.' - этот ответ нарушает правила 8 и 9 + +'Тогда, возможно, проблема связана с настройками самого приложения или сервера. +В этом случае я рекомендую обратиться к вашему системному администратору или в службу поддержки вашего VPN-провайдера. +Они смогут провести более детальную диагностику и устранить проблему.' - этот ответ нарушает правила 8 и 12 +""" + + +knowledge = """ +Инструкции: +Как пользоваться openvpn: +Документ представляет собой инструкцию по подключению к корпоративной сети через VPN, +используя приложение OpenVPN Connect. Он предназначен для сотрудников компании, +которым необходимо получить удалённый доступ к внутренним ресурсам из внешней сети +(например, при работе из дома или вне офиса). + +Ключевые шаги подключения к VPN: + +Подключение к внешней сети: +Убедитесь, что устройство подключено к любой внешней сети: через Ethernet, Wi-Fi или мобильную точку доступа. +Статус подключения можно проверить через иконку интернета в нижнем правом углу экрана. +Запуск OpenVPN Connect: +Найдите и запустите приложение OpenVPN Connect. +Оно может находиться на рабочем столе или его можно найти через строку поиска Windows, введя «OpenVPN Connect». +Активация VPN-соединения: +Переключите ползунок подключения в приложении, чтобы инициировать подключение к VPN. +В появившемся окне введите свои учётные данные (логин и пароль). +Нажмите кнопку «ОК» для подтверждения. +Готово: +После авторизации будет установлен удалённый доступ к сетевым ресурсам компании. +Цель документа: +Обеспечить безопасный доступ сотрудников к корпоративным системам из внешней интернет-сети посредством OpenVPN. + +Требования: +Установленное приложение OpenVPN Connect. +Доступ к внешнему интернету. +Действующие учётные данные сотрудника. + +Как проверить, установлена ли программа open vpn: +В поиске набрать OpenVPN. Если результаты поиска отсутствуют, то программа не установлена. + + +Как проверить, подключился ли open vpn: +Успех подключения выглядит как на скриншоте, должна появиться надпись CONNECTED. + +Как правильно вводить учетные данные: +Логин вводится в формате Имя.Фамилия на латинице с заглавных букв. Пример: Ivan.Ivanov. Пароль вводится такой же, как при входе в пк. +""" + +SYSTEM_PROMPT = SYSTEM_PROMPT_TEMPLATE.format(service_name=service_name, + service_description=service_description, + cases=cases, + broken_cases=broken_cases, + knowledge=knowledge) diff --git a/services/openvpn/openvpn_service.py b/.history/src/openvpn/openvpn_service_20250627125242.py similarity index 100% rename from services/openvpn/openvpn_service.py rename to .history/src/openvpn/openvpn_service_20250627125242.py diff --git a/.history/src/openvpn/openvpn_service_20250630135358.py b/.history/src/openvpn/openvpn_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..aa5e383c0ef75442caffaeac2c49d866a7a2e104 --- /dev/null +++ b/.history/src/openvpn/openvpn_service_20250630135358.py @@ -0,0 +1,44 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.openvpn.openvpn_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class OpenVPNService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.script_part, + self.install_program, + self.delete_and_install_program + ] + + @tool + def script_part(question: str) -> None: + """Запускаем большой скрипт для проверки возможных проблем с приложением + и их устранением""" + print("ran_graph(question)") + time.sleep(4) + logger.info("script part") + + @tool + def install_program(question: str) -> None: + """Запускаем скрипты, которые ставят программу openvpn""" + print("### Скриптом устанавливаем программу") + time.sleep(3) + logger.info("installing program") + + @tool + def delete_and_install_program(question: str) -> None: + """Запускаем скрипты, которые удаляют и ставят программу openvpn""" + print("### Скриптом удаляем и ставим программу") + time.sleep(3) + logger.info("deleting and installing program") diff --git a/services/print_and_scan/print_and_scan_service.py b/.history/src/print_and_scan/print_and_scan_service_20250630021904.py similarity index 100% rename from services/print_and_scan/print_and_scan_service.py rename to .history/src/print_and_scan/print_and_scan_service_20250630021904.py diff --git a/.history/src/print_and_scan/print_and_scan_service_20250630135358.py b/.history/src/print_and_scan/print_and_scan_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..e017323be2d72bd6e9e0ed8c33ff8a2bd2a9dec3 --- /dev/null +++ b/.history/src/print_and_scan/print_and_scan_service_20250630135358.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.print_and_scan.print_and_scan_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class PrintAndScanService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/service_by_name.py b/.history/src/service_by_name_20250630022251.py similarity index 100% rename from services/service_by_name.py rename to .history/src/service_by_name_20250630022251.py diff --git a/.history/src/service_by_name_20250630135358.py b/.history/src/service_by_name_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..c09e69bf7f3c5bbf6aa8f38dc98b7e64cdfb30d1 --- /dev/null +++ b/.history/src/service_by_name_20250630135358.py @@ -0,0 +1,29 @@ + +def get_service_by_name(service_name): + if service_name == "openvpn": + from src.openvpn.openvpn_service import OpenVPNService + return OpenVPNService() + if service_name == "corporate_email": + from src.corporate_email.corporate_email_service import CorporateEmailService + return CorporateEmailService() + if service_name == "internet_access": + from src.internet_access.internet_access_service import InternetAccessService + return InternetAccessService() + if service_name == "b2b": + from src.b2b.b2b_service import B2BService + return B2BService() + if service_name == "crm": + from src.crm.crm_service import CRMService + return CRMService() + if service_name == "one_c": + from src.one_c.one_c_service import OneCService + return OneCService() + if service_name == "borlas": + from src.borlas.borlas_service import BorlasService + return BorlasService() + if service_name == "print_and_scan": + from src.print_and_scan.print_and_scan_service import PrintAndScanService + return PrintAndScanService() + + + raise ValueError(f"Некорректное имя сервиса '{service_name}'") diff --git a/services/streamlit_app.py b/.history/src/streamlit_app_20250630025024.py similarity index 100% rename from services/streamlit_app.py rename to .history/src/streamlit_app_20250630025024.py diff --git a/.history/src/streamlit_app_20250630135358.py b/.history/src/streamlit_app_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..39e277d0085d61401a4f7b6f26afee9df27bfcba --- /dev/null +++ b/.history/src/streamlit_app_20250630135358.py @@ -0,0 +1,101 @@ +import streamlit as st +from langchain_core.messages import HumanMessage, AIMessage +from src.get_classification import get_graph_class +from datetime import datetime + + +def message_to_dict(messages): + result = [] + for message in messages: + if isinstance(message, HumanMessage) or isinstance(message, AIMessage): + print("message:", message.content) + if message.content == "" or message.content is None: + continue + if isinstance(message, HumanMessage): + result.append({"role": "user", "content": message.content}) + elif isinstance(message, AIMessage): + result.append({"role": "assistant", "content": message.content}) + print("-" * 100) + return result + + +def find_last_bot_message(messages): + """Находит последнее сообщение бота""" + for message in messages[::-1]: + if isinstance(message, AIMessage) and len(message.content) > 0: + return message.content + return None + + +def display_chat_messages(): + """Отображает историю сообщений в чате""" + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + + +def save_broken_case(): + messages_dict = st.session_state.messages + result_str = "" + for elem in messages_dict: + role = elem["role"] + content = elem["content"] + result_str += f"{role}: {content}\n" + + current_datetime = datetime.now() + formatted_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S") + + with open("/Users/admin/my_documents/retrieval_part/services/broken_cases.txt", "a") as file: + file.write(f"{formatted_datetime}\n" + result_str + "\n" + "-" * 50 + "\n\n") + + +def handle_user_input(): + """Обрабатывает ввод пользователя и генерирует ответ бота""" + if prompt := st.chat_input("Введите ваш вопрос"): + st.session_state.messages.append({"role": "user", "content": prompt}) + if prompt.lower().startswith("log"): + save_broken_case() + st.session_state.messages = [] + display_chat_messages() + else: + with st.chat_message("user"): + st.markdown(prompt) + + if "bot" not in st.session_state: + st.session_state.bot = get_graph_class(prompt) + st.session_state.bot.invoke(prompt) + + # Извлекаем последнее сообщение бота + last_bot_message = find_last_bot_message(st.session_state.bot.messages) + st.session_state.messages.append( + {"role": "assistant", "content": last_bot_message} + ) + with st.chat_message("assistant"): + st.markdown(last_bot_message) + + +def clear_chat(): + """Очищает чат и пересоздает бота""" + st.session_state.messages = [] + del st.session_state.bot + + +def main(): + """Основная функция приложения""" + # Заголовок приложения + st.title("Чат-бот технической поддержки OpenVPN") + + # Кнопка очистки чата + if st.button("Clear"): + clear_chat() + + if "messages" not in st.session_state: + st.session_state.messages = [] + + # Отображение чата и обработка ввода + display_chat_messages() + handle_user_input() + + +if __name__ == "__main__": + main() diff --git a/services/template/_service.py b/.history/src/template/_service_20250627160824.py similarity index 100% rename from services/template/_service.py rename to .history/src/template/_service_20250627160824.py diff --git a/.history/src/template/_service_20250630135358.py b/.history/src/template/_service_20250630135358.py new file mode 100644 index 0000000000000000000000000000000000000000..32ffc8159bd69c5b8952488583fbb220d6debfd8 --- /dev/null +++ b/.history/src/template/_service_20250630135358.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.template._prompts import SYSTEM_PROMPT ## FIX +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class ExamplePLSRenameService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/.DS_Store b/src/.DS_Store similarity index 100% rename from services/.DS_Store rename to src/.DS_Store diff --git a/services/__init__.py b/src/__init__.py similarity index 100% rename from services/__init__.py rename to src/__init__.py diff --git a/services/b2b/__init__.py b/src/b2b/__init__.py similarity index 100% rename from services/b2b/__init__.py rename to src/b2b/__init__.py diff --git a/services/b2b/b2b_prompts.py b/src/b2b/b2b_prompts.py similarity index 100% rename from services/b2b/b2b_prompts.py rename to src/b2b/b2b_prompts.py diff --git a/src/b2b/b2b_service.py b/src/b2b/b2b_service.py new file mode 100644 index 0000000000000000000000000000000000000000..4e141d76a73045a52dd19e92bdf6c63196acdc67 --- /dev/null +++ b/src/b2b/b2b_service.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.b2b.b2b_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class B2BService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/src/base_graph.py b/src/base_graph.py new file mode 100644 index 0000000000000000000000000000000000000000..c391573bed5f45b83c459a54f1eee0c91777ca94 --- /dev/null +++ b/src/base_graph.py @@ -0,0 +1,126 @@ +from typing import Annotated, Type +from langgraph.graph import StateGraph +from langchain_core.messages import HumanMessage, ToolMessage +from langgraph.graph.message import add_messages +from typing_extensions import TypedDict +from src.base_service import BaseService +from src.get_answer_gigachat import AnswerGigaChat +import logging +import os + +logger = logging.getLogger(__name__) + +class State(TypedDict): + messages: Annotated[list, add_messages] + +class BaseGraph: + def __init__(self, service: Type[BaseService]): + self.service = service + # self._initialize_logging() + self.tools_dict = {tool.name: tool for tool in service.tools} + self.llm_with_tools = AnswerGigaChat().bind_tools(service.tools) + self.messages = service.get_initial_messages() + self.graph = self._build_graph() + logger.info(f"BaseGraph with service {service} was built") + + def rebuild_with_new_service(self, service: Type[BaseService]): + self.service = service + self.tools_dict = {tool.name: tool for tool in service.tools} + self.llm_with_tools = AnswerGigaChat().bind_tools(service.tools) + self.graph = self._build_graph() + self.messages = service.get_messages_from_redirect(self.messages) + logger.info(f"BaseGraph was rebuilt with service {service}") + + def _initialize_logging(self): + if os.path.exists(self.service.log_path): + os.remove(self.service.log_path) + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[logging.FileHandler(self.service.log_path)] + ) + + def _agent_node(self, state: State): + try: + logger.info("Starting agent_node") + messages = state["messages"] + response = self.llm_with_tools.invoke(messages) + response.content = self._clean_response(response.content) + return {"messages": [response]} + except Exception as e: + logger.error(f"Error in agent_node: {str(e)}", exc_info=True) + raise + + def _tool_node(self, state: State): + try: + logger.info("Starting tool_node") + last_message = state["messages"][-1] + tool_calls = last_message.tool_calls + + results = [] + for call in tool_calls: + tool_name = call["name"] + logger.info(f"Running tool {tool_name}") + args = call["args"] + + tool = self.tools_dict.get(tool_name) + if not tool: + raise ValueError(f"Tool {tool_name} not found") + + tool_response = tool.invoke(args) + if tool_name == "make_redirect": + self.rebuild_with_new_service(tool_response) + results.append(str(tool_response)) + + return {"messages": [ToolMessage(content=", ".join(results), tool_call_id=call["id"])]} + except Exception as e: + logger.error(f"Error in tool_node: {str(e)}", exc_info=True) + raise + + def _should_continue(self, state: State): + try: + logger.info("Checking should continue") + last_message = state["messages"][-1] + return "tool" if "function_call" in last_message.additional_kwargs else "end" + except Exception as e: + logger.error(f"Error in should_continue: {str(e)}", exc_info=True) + raise + + def _build_graph(self): + try: + logger.info("Building graph") + graph_builder = StateGraph(State) + + graph_builder.add_node("agent", self._agent_node) + graph_builder.add_node("tool", self._tool_node) + + graph_builder.add_conditional_edges( + "agent", + self._should_continue, + {"tool": "tool", "end": "__end__"} + ) + graph_builder.add_edge("tool", "agent") + graph_builder.set_entry_point("agent") + + return graph_builder.compile() + except Exception as e: + logger.error(f"Error building graph: {str(e)}", exc_info=True) + raise + + def _clean_response(self, content: str) -> str: + content = content.replace("", "").replace("", "") + if "" in content: + content = content.split("")[1] + if "" in content: + content = content.split("")[-1] + return content + + def invoke(self, user_input): + try: + self.messages.append(HumanMessage(content=user_input)) + result = self.graph.invoke({"messages": self.messages}) + self.messages = result["messages"] + return result + except Exception as e: + logger.error(f"Error invoking graph: {str(e)}", exc_info=True) + raise diff --git a/src/base_service.py b/src/base_service.py new file mode 100644 index 0000000000000000000000000000000000000000..dea09b6af9bc847bfee045f0da90364fd724f527 --- /dev/null +++ b/src/base_service.py @@ -0,0 +1,76 @@ +from abc import ABC, abstractmethod +from langchain_core.messages import SystemMessage, HumanMessage, AIMessage +from system_prompt_template import DIALOG_PROMPT +from typing import List +from langchain_core.tools import BaseTool, tool +from src.service_by_name import get_service_by_name +import logging +import time +import os +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +class BaseService(ABC): + def __init__(self): + self._base_tools = [self.closing_task, + self.make_jira_task, + ] + + @property + @abstractmethod + def system_prompt(self) -> str: + """Возвращает системный промпт для сервиса""" + pass + + @property + @abstractmethod + def tools(self) -> List[BaseTool]: + """Возвращает список инструментов сервиса""" + pass + + @property + def log_path(self) -> str: + """Возвращает путь до папки с логами""" + return os.getenv("LOG_SERVICES_PATH") + + def get_initial_messages(self): + """Возвращает начальные сообщения для графа""" + return [SystemMessage(content=self.system_prompt)] + + def get_messages_from_redirect(self, messages): + messages_formatted = "" + for message in messages: + if isinstance(message, HumanMessage): + message += f"user: '{message.content}'\n" + elif isinstance(message, AIMessage): + message += f"assistant: '{message.content}'\n" + + return [SystemMessage(content=self.system_prompt + DIALOG_PROMPT.format(messages_formatted))] + + @tool + def make_redirect(service_name: str): + """Когда оказывается, что пользователю нужен эксперт в другом сервисе, + пользователя нужно перенаправить на соответствующего эксперта. + Функция принимает аргумент service_name из списка: + ['openvpn', 'corporate_email', 'internet_access'] и пересоздает класс эксперта + с соответствующими новому сервису знаниями.""" + logger.info("redirecting question to service {service_name}") + return get_service_by_name(service_name) + + + @tool + def closing_task(question: str) -> None: + """Задача выполнена, сохраняем результаты""" + print("### Задача выполнена, сохраняем результаты") + time.sleep(2) + logger.info("task closed") + + @tool + def make_jira_task(question: str) -> None: + """Задача не может быть выполнена с помощью ии, создаем задачу в jira для того, + чтобы её выполнил человек""" + time.sleep(2) + print("### Скриптом создается задача в jira с соответствующими деталями") + logger.info("jira task was make") diff --git a/services/borlas/__init__.py b/src/borlas/__init__.py similarity index 100% rename from services/borlas/__init__.py rename to src/borlas/__init__.py diff --git a/services/borlas/borlas_prompts.py b/src/borlas/borlas_prompts.py similarity index 100% rename from services/borlas/borlas_prompts.py rename to src/borlas/borlas_prompts.py diff --git a/src/borlas/borlas_service.py b/src/borlas/borlas_service.py new file mode 100644 index 0000000000000000000000000000000000000000..d1bae7c3779ada7c769b4d722b2a71dea2dfb9ff --- /dev/null +++ b/src/borlas/borlas_service.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.borlas.borlas_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class BorlasService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/broken_cases.txt b/src/broken_cases.txt similarity index 100% rename from services/broken_cases.txt rename to src/broken_cases.txt diff --git a/services/corporate_email/__init__.py b/src/corporate_email/__init__.py similarity index 100% rename from services/corporate_email/__init__.py rename to src/corporate_email/__init__.py diff --git a/services/corporate_email/corporate_email_prompts.py b/src/corporate_email/corporate_email_prompts.py similarity index 100% rename from services/corporate_email/corporate_email_prompts.py rename to src/corporate_email/corporate_email_prompts.py diff --git a/src/corporate_email/corporate_email_service.py b/src/corporate_email/corporate_email_service.py new file mode 100644 index 0000000000000000000000000000000000000000..8b13b53638de53410472b11a7e3a614bd2a0b21e --- /dev/null +++ b/src/corporate_email/corporate_email_service.py @@ -0,0 +1,24 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.corporate_email.corporate_email_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class CorporateEmailService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [] + + @tool + def check_lock_in_active_dir_and_password(question: str) -> None: + """Проверка блокировки учетной записи сотрудника в системе и разблокировка и проверка на просроченный пароль и обновление пароля. Если эти проблемы вскрываются, то функция их исправляет.""" + time.sleep(4) + logger.info("check_lock_in_active_dir_and_password") diff --git a/services/crm/__init__.py b/src/crm/__init__.py similarity index 100% rename from services/crm/__init__.py rename to src/crm/__init__.py diff --git a/services/crm/crm_prompts.py b/src/crm/crm_prompts.py similarity index 100% rename from services/crm/crm_prompts.py rename to src/crm/crm_prompts.py diff --git a/src/crm/crm_service.py b/src/crm/crm_service.py new file mode 100644 index 0000000000000000000000000000000000000000..9518a955cebbf220551d8fb5eecf5995acbbd987 --- /dev/null +++ b/src/crm/crm_service.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.crm.crm_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class CRMService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/get_answer_gigachat.py b/src/get_answer_gigachat.py similarity index 100% rename from services/get_answer_gigachat.py rename to src/get_answer_gigachat.py diff --git a/services/get_classification.py b/src/get_classification.py similarity index 97% rename from services/get_classification.py rename to src/get_classification.py index 1164561116497368354f78314c7f7821a0c70dcf..8ac712e6ca930afbc86d25a047c2188b636938c1 100644 --- a/services/get_classification.py +++ b/src/get_classification.py @@ -3,9 +3,9 @@ from typing import Literal import logging from langchain.prompts import ChatPromptTemplate from langchain_core.output_parsers import JsonOutputParser -from services.get_answer_gigachat import AnswerGigaChat -from services.service_by_name import get_service_by_name -from services.base_graph import BaseGraph +from src.get_answer_gigachat import AnswerGigaChat +from src.service_by_name import get_service_by_name +from src.base_graph import BaseGraph import os from dotenv import load_dotenv diff --git a/services/internet_access/__init__.py b/src/internet_access/__init__.py similarity index 100% rename from services/internet_access/__init__.py rename to src/internet_access/__init__.py diff --git a/services/internet_access/internet_access_cases.json b/src/internet_access/internet_access_cases.json similarity index 100% rename from services/internet_access/internet_access_cases.json rename to src/internet_access/internet_access_cases.json diff --git a/services/internet_access/internet_access_prompts.py b/src/internet_access/internet_access_prompts.py similarity index 100% rename from services/internet_access/internet_access_prompts.py rename to src/internet_access/internet_access_prompts.py diff --git a/src/internet_access/internet_access_service.py b/src/internet_access/internet_access_service.py new file mode 100644 index 0000000000000000000000000000000000000000..317737aaf6244e1b74d320803afa0d6fc693b257 --- /dev/null +++ b/src/internet_access/internet_access_service.py @@ -0,0 +1,27 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.internet_access.internet_access_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + +class InternetAccessService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/just_play.ipynb b/src/just_play.ipynb similarity index 100% rename from services/just_play.ipynb rename to src/just_play.ipynb diff --git a/services/one_c/__init__.py b/src/one_c/__init__.py similarity index 100% rename from services/one_c/__init__.py rename to src/one_c/__init__.py diff --git a/services/one_c/one_c_prompts.py b/src/one_c/one_c_prompts.py similarity index 100% rename from services/one_c/one_c_prompts.py rename to src/one_c/one_c_prompts.py diff --git a/src/one_c/one_c_service.py b/src/one_c/one_c_service.py new file mode 100644 index 0000000000000000000000000000000000000000..04aa455b82dec5865db1e1cc017e395a386a0a31 --- /dev/null +++ b/src/one_c/one_c_service.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.one_c.one_c_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class OneCService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/services/openvpn/__init__.py b/src/openvpn/__init__.py similarity index 100% rename from services/openvpn/__init__.py rename to src/openvpn/__init__.py diff --git a/src/openvpn/openvpn_prompts.py b/src/openvpn/openvpn_prompts.py new file mode 100644 index 0000000000000000000000000000000000000000..d4fa9cafc1f8d2b25923be34985277b82e4d27b8 --- /dev/null +++ b/src/openvpn/openvpn_prompts.py @@ -0,0 +1,103 @@ +from src.system_prompt_template import SYSTEM_PROMPT_TEMPLATE + +service_name = "open vpn" + +service_description = "" + +cases = """ +У пользователя может быть одна из 3 ситуаций, надо определить, какая у него: + +- Консультация. + Это когда: + - Программа есть + - Программа открывается + - vpn подключается + В этом случае надо предпринять действия: + - Проконсультировать пользователя, как пользоваться vpn. + и ответить на его вопросы. + - Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение. + в техническую поддержку + - Если ты не можешь помочь пользователю или он задает вопросы, на которые у тебя нет инструкций, + вызови функцию make_jira_task, сообщи, что была создана задача в jira и в ближайшее времся с + пользователем свяжется сотрудник технической поддержки. + +- Установка + Это когда: + - Программы нет на компьютере пользователя. + В этом случае надо предпринять действия: + - Вызывать функцию install_program + - Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение + в техническую поддержку + - Если пользователь говорит, что vpn по-прежнему не работает, вызови функцию script_part + +- Переустановка + Это когда: + - Программы не открывается. + В этом случае надо предпринять действия: + - Вызывать функцию delete_and_install_program + - Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение + в техническую поддержку + - Если пользователь говорит, что vpn по-прежнему не работает, вызови функцию script_part +""" + + +broken_cases = """ +'В таком случае вам необходимо установить программу OpenVPN. Для этого выполните следующие действия: + +Перейдите на официальный сайт OpenVPN (https://openvpn.net/) и скачайте последнюю версию программы. +Запустите установочный файл и следуйте инструкциям мастера установки. +После завершения установки запустите программу и попробуйте подключить VPN. +Если возникнут трудности при установке или использовании программы, пожалуйста, дайте мне знать.' - этот ответ нарушает правила 8 и 9 + +'Тогда, возможно, проблема связана с настройками самого приложения или сервера. +В этом случае я рекомендую обратиться к вашему системному администратору или в службу поддержки вашего VPN-провайдера. +Они смогут провести более детальную диагностику и устранить проблему.' - этот ответ нарушает правила 8 и 12 +""" + + +knowledge = """ +Инструкции: +Как пользоваться openvpn: +Документ представляет собой инструкцию по подключению к корпоративной сети через VPN, +используя приложение OpenVPN Connect. Он предназначен для сотрудников компании, +которым необходимо получить удалённый доступ к внутренним ресурсам из внешней сети +(например, при работе из дома или вне офиса). + +Ключевые шаги подключения к VPN: + +Подключение к внешней сети: +Убедитесь, что устройство подключено к любой внешней сети: через Ethernet, Wi-Fi или мобильную точку доступа. +Статус подключения можно проверить через иконку интернета в нижнем правом углу экрана. +Запуск OpenVPN Connect: +Найдите и запустите приложение OpenVPN Connect. +Оно может находиться на рабочем столе или его можно найти через строку поиска Windows, введя «OpenVPN Connect». +Активация VPN-соединения: +Переключите ползунок подключения в приложении, чтобы инициировать подключение к VPN. +В появившемся окне введите свои учётные данные (логин и пароль). +Нажмите кнопку «ОК» для подтверждения. +Готово: +После авторизации будет установлен удалённый доступ к сетевым ресурсам компании. +Цель документа: +Обеспечить безопасный доступ сотрудников к корпоративным системам из внешней интернет-сети посредством OpenVPN. + +Требования: +Установленное приложение OpenVPN Connect. +Доступ к внешнему интернету. +Действующие учётные данные сотрудника. + +Как проверить, установлена ли программа open vpn: +В поиске набрать OpenVPN. Если результаты поиска отсутствуют, то программа не установлена. + + +Как проверить, подключился ли open vpn: +Успех подключения выглядит как на скриншоте, должна появиться надпись CONNECTED. + +Как правильно вводить учетные данные: +Логин вводится в формате Имя.Фамилия на латинице с заглавных букв. Пример: Ivan.Ivanov. Пароль вводится такой же, как при входе в пк. +""" + +SYSTEM_PROMPT = SYSTEM_PROMPT_TEMPLATE.format(service_name=service_name, + service_description=service_description, + cases=cases, + broken_cases=broken_cases, + knowledge=knowledge) diff --git a/src/openvpn/openvpn_service.py b/src/openvpn/openvpn_service.py new file mode 100644 index 0000000000000000000000000000000000000000..aa5e383c0ef75442caffaeac2c49d866a7a2e104 --- /dev/null +++ b/src/openvpn/openvpn_service.py @@ -0,0 +1,44 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.openvpn.openvpn_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class OpenVPNService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.script_part, + self.install_program, + self.delete_and_install_program + ] + + @tool + def script_part(question: str) -> None: + """Запускаем большой скрипт для проверки возможных проблем с приложением + и их устранением""" + print("ran_graph(question)") + time.sleep(4) + logger.info("script part") + + @tool + def install_program(question: str) -> None: + """Запускаем скрипты, которые ставят программу openvpn""" + print("### Скриптом устанавливаем программу") + time.sleep(3) + logger.info("installing program") + + @tool + def delete_and_install_program(question: str) -> None: + """Запускаем скрипты, которые удаляют и ставят программу openvpn""" + print("### Скриптом удаляем и ставим программу") + time.sleep(3) + logger.info("deleting and installing program") diff --git a/services/print_and_scan/__init__.py b/src/print_and_scan/__init__.py similarity index 100% rename from services/print_and_scan/__init__.py rename to src/print_and_scan/__init__.py diff --git a/services/print_and_scan/print_and_scan_prompts.py b/src/print_and_scan/print_and_scan_prompts.py similarity index 100% rename from services/print_and_scan/print_and_scan_prompts.py rename to src/print_and_scan/print_and_scan_prompts.py diff --git a/src/print_and_scan/print_and_scan_service.py b/src/print_and_scan/print_and_scan_service.py new file mode 100644 index 0000000000000000000000000000000000000000..e017323be2d72bd6e9e0ed8c33ff8a2bd2a9dec3 --- /dev/null +++ b/src/print_and_scan/print_and_scan_service.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.print_and_scan.print_and_scan_prompts import SYSTEM_PROMPT +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class PrintAndScanService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") + diff --git a/src/service_by_name.py b/src/service_by_name.py new file mode 100644 index 0000000000000000000000000000000000000000..c09e69bf7f3c5bbf6aa8f38dc98b7e64cdfb30d1 --- /dev/null +++ b/src/service_by_name.py @@ -0,0 +1,29 @@ + +def get_service_by_name(service_name): + if service_name == "openvpn": + from src.openvpn.openvpn_service import OpenVPNService + return OpenVPNService() + if service_name == "corporate_email": + from src.corporate_email.corporate_email_service import CorporateEmailService + return CorporateEmailService() + if service_name == "internet_access": + from src.internet_access.internet_access_service import InternetAccessService + return InternetAccessService() + if service_name == "b2b": + from src.b2b.b2b_service import B2BService + return B2BService() + if service_name == "crm": + from src.crm.crm_service import CRMService + return CRMService() + if service_name == "one_c": + from src.one_c.one_c_service import OneCService + return OneCService() + if service_name == "borlas": + from src.borlas.borlas_service import BorlasService + return BorlasService() + if service_name == "print_and_scan": + from src.print_and_scan.print_and_scan_service import PrintAndScanService + return PrintAndScanService() + + + raise ValueError(f"Некорректное имя сервиса '{service_name}'") diff --git a/src/streamlit_app.py b/src/streamlit_app.py new file mode 100644 index 0000000000000000000000000000000000000000..39e277d0085d61401a4f7b6f26afee9df27bfcba --- /dev/null +++ b/src/streamlit_app.py @@ -0,0 +1,101 @@ +import streamlit as st +from langchain_core.messages import HumanMessage, AIMessage +from src.get_classification import get_graph_class +from datetime import datetime + + +def message_to_dict(messages): + result = [] + for message in messages: + if isinstance(message, HumanMessage) or isinstance(message, AIMessage): + print("message:", message.content) + if message.content == "" or message.content is None: + continue + if isinstance(message, HumanMessage): + result.append({"role": "user", "content": message.content}) + elif isinstance(message, AIMessage): + result.append({"role": "assistant", "content": message.content}) + print("-" * 100) + return result + + +def find_last_bot_message(messages): + """Находит последнее сообщение бота""" + for message in messages[::-1]: + if isinstance(message, AIMessage) and len(message.content) > 0: + return message.content + return None + + +def display_chat_messages(): + """Отображает историю сообщений в чате""" + for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.markdown(message["content"]) + + +def save_broken_case(): + messages_dict = st.session_state.messages + result_str = "" + for elem in messages_dict: + role = elem["role"] + content = elem["content"] + result_str += f"{role}: {content}\n" + + current_datetime = datetime.now() + formatted_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S") + + with open("/Users/admin/my_documents/retrieval_part/services/broken_cases.txt", "a") as file: + file.write(f"{formatted_datetime}\n" + result_str + "\n" + "-" * 50 + "\n\n") + + +def handle_user_input(): + """Обрабатывает ввод пользователя и генерирует ответ бота""" + if prompt := st.chat_input("Введите ваш вопрос"): + st.session_state.messages.append({"role": "user", "content": prompt}) + if prompt.lower().startswith("log"): + save_broken_case() + st.session_state.messages = [] + display_chat_messages() + else: + with st.chat_message("user"): + st.markdown(prompt) + + if "bot" not in st.session_state: + st.session_state.bot = get_graph_class(prompt) + st.session_state.bot.invoke(prompt) + + # Извлекаем последнее сообщение бота + last_bot_message = find_last_bot_message(st.session_state.bot.messages) + st.session_state.messages.append( + {"role": "assistant", "content": last_bot_message} + ) + with st.chat_message("assistant"): + st.markdown(last_bot_message) + + +def clear_chat(): + """Очищает чат и пересоздает бота""" + st.session_state.messages = [] + del st.session_state.bot + + +def main(): + """Основная функция приложения""" + # Заголовок приложения + st.title("Чат-бот технической поддержки OpenVPN") + + # Кнопка очистки чата + if st.button("Clear"): + clear_chat() + + if "messages" not in st.session_state: + st.session_state.messages = [] + + # Отображение чата и обработка ввода + display_chat_messages() + handle_user_input() + + +if __name__ == "__main__": + main() diff --git a/services/system_prompt_template.py b/src/system_prompt_template.py similarity index 100% rename from services/system_prompt_template.py rename to src/system_prompt_template.py diff --git a/services/template/__init__.py b/src/template/__init__.py similarity index 100% rename from services/template/__init__.py rename to src/template/__init__.py diff --git a/services/template/_prompts.py b/src/template/_prompts.py similarity index 100% rename from services/template/_prompts.py rename to src/template/_prompts.py diff --git a/src/template/_service.py b/src/template/_service.py new file mode 100644 index 0000000000000000000000000000000000000000..32ffc8159bd69c5b8952488583fbb220d6debfd8 --- /dev/null +++ b/src/template/_service.py @@ -0,0 +1,28 @@ +from langchain_core.tools import BaseTool, tool +from src.base_service import BaseService +from src.template._prompts import SYSTEM_PROMPT ## FIX +from typing import List +import logging +import time + +logger = logging.getLogger(__name__) + + +class ExamplePLSRenameService(BaseService): + @property + def system_prompt(self) -> str: + return SYSTEM_PROMPT + + @property + def tools(self) -> List[BaseTool]: + return self._base_tools + [ + self.example_func, + ] + + @tool + def example_func(question: str) -> None: + """Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании""" + print("Я example_func") + time.sleep(4) + logger.info("Кто-то забыл удалить example_func!") +