Spaces:
Running
Running
Boris
commited on
Commit
·
d1254fd
1
Parent(s):
9f35c6f
init
Browse files- __init__.py +0 -0
- app.py +80 -50
- b2b/__init__.py +0 -0
- b2b/b2b_prompts.py +59 -0
- b2b/b2b_service.py +28 -0
- base_graph.py +116 -0
- base_service.py +76 -0
- borlas/__init__.py +0 -0
- borlas/borlas_prompts.py +68 -0
- borlas/borlas_service.py +28 -0
- broken_cases.txt +0 -0
- corporate_email/__init__.py +0 -0
- corporate_email/corporate_email_prompts.py +85 -0
- corporate_email/corporate_email_service.py +24 -0
- crm/__init__.py +0 -0
- crm/crm_prompts.py +3 -0
- crm/crm_service.py +28 -0
- get_answer_gigachat.py +147 -0
- get_classification.py +78 -0
- internet_access/__init__.py +0 -0
- internet_access/internet_access_cases.json +124 -0
- internet_access/internet_access_prompts.py +59 -0
- internet_access/internet_access_service.py +27 -0
- one_c/__init__.py +0 -0
- one_c/one_c_prompts.py +51 -0
- one_c/one_c_service.py +28 -0
- openvpn/__init__.py +0 -0
- openvpn/openvpn_prompts.py +103 -0
- openvpn/openvpn_service.py +44 -0
- print_and_scan/__init__.py +0 -0
- print_and_scan/print_and_scan_prompts.py +52 -0
- print_and_scan/print_and_scan_service.py +28 -0
- requirements.txt +68 -1
- service_by_name.py +29 -0
- system_prompt_template.py +98 -0
- template/__init__.py +0 -0
- template/_prompts.py +3 -0
- template/_service.py +28 -0
__init__.py
ADDED
File without changes
|
app.py
CHANGED
@@ -1,64 +1,94 @@
|
|
1 |
import gradio as gr
|
2 |
-
from
|
|
|
|
|
3 |
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
-
def
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
if val[0]:
|
22 |
-
messages.append({"role": "user", "content": val[0]})
|
23 |
-
if val[1]:
|
24 |
-
messages.append({"role": "assistant", "content": val[1]})
|
25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
messages.append({"role": "user", "content": message})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
temperature=temperature,
|
35 |
-
top_p=top_p,
|
36 |
-
):
|
37 |
-
token = message.choices[0].delta.content
|
38 |
-
|
39 |
-
response += token
|
40 |
-
yield response
|
41 |
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
""
|
46 |
-
|
47 |
-
respond,
|
48 |
additional_inputs=[
|
49 |
-
gr.
|
50 |
-
gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
|
51 |
-
gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
|
52 |
-
gr.Slider(
|
53 |
-
minimum=0.1,
|
54 |
-
maximum=1.0,
|
55 |
-
value=0.95,
|
56 |
-
step=0.05,
|
57 |
-
label="Top-p (nucleus sampling)",
|
58 |
-
),
|
59 |
],
|
|
|
|
|
|
|
|
|
|
|
60 |
)
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
|
|
63 |
if __name__ == "__main__":
|
64 |
-
|
|
|
1 |
import gradio as gr
|
2 |
+
from langchain_core.messages import HumanMessage, AIMessage
|
3 |
+
from get_classification import get_graph_class
|
4 |
+
from datetime import datetime
|
5 |
|
6 |
+
# Глобальные переменные состояния (аналог session_state в Streamlit)
|
7 |
+
messages = []
|
8 |
+
bot_instance = None
|
|
|
9 |
|
10 |
+
def find_last_bot_message(messages_list):
|
11 |
+
"""Находит последнее сообщение бота"""
|
12 |
+
for message in messages_list[::-1]:
|
13 |
+
if isinstance(message, AIMessage) and len(message.content) > 0:
|
14 |
+
return message.content
|
15 |
+
return None
|
16 |
|
17 |
+
def save_broken_case(messages_dict):
|
18 |
+
"""Сохраняет 'сломанные' кейсы (заглушка)"""
|
19 |
+
result_str = ""
|
20 |
+
for elem in messages_dict:
|
21 |
+
role = elem["role"]
|
22 |
+
content = elem["content"]
|
23 |
+
result_str += f"{role}: {content}\n"
|
24 |
+
|
25 |
+
current_datetime = datetime.now()
|
26 |
+
formatted_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S")
|
27 |
+
print(f"Saved broken case at {formatted_datetime}:\n{result_str}")
|
|
|
|
|
|
|
|
|
28 |
|
29 |
+
def predict(message, history):
|
30 |
+
"""Обработка ввода пользователя и генерация ответа"""
|
31 |
+
global messages, bot_instance
|
32 |
+
|
33 |
+
# Обработка команды log
|
34 |
+
if message.lower().startswith("log"):
|
35 |
+
save_broken_case(messages)
|
36 |
+
messages = []
|
37 |
+
return ""
|
38 |
+
|
39 |
+
# Добавляем сообщение пользователя
|
40 |
messages.append({"role": "user", "content": message})
|
41 |
+
|
42 |
+
try:
|
43 |
+
# Инициализация бота при первом запросе
|
44 |
+
if bot_instance is None:
|
45 |
+
bot_instance = get_graph_class(message)
|
46 |
+
|
47 |
+
# Вызываем бота
|
48 |
+
bot_instance.invoke(message)
|
49 |
+
|
50 |
+
# Извлекаем последний ответ бота
|
51 |
+
last_bot_message = find_last_bot_message(bot_instance.messages)
|
52 |
+
last_bot_message = last_bot_message or "Извините, не удалось сгенерировать ответ"
|
53 |
+
|
54 |
+
# Добавляем ответ в историю
|
55 |
+
messages.append({"role": "assistant", "content": last_bot_message})
|
56 |
+
return last_bot_message
|
57 |
+
|
58 |
+
except Exception as e:
|
59 |
+
error_msg = f"Ошибка: {str(e)}"
|
60 |
+
messages.append({"role": "assistant", "content": error_msg})
|
61 |
+
return error_msg
|
62 |
|
63 |
+
def clear_chat():
|
64 |
+
"""Очистка чата и сброс состояния"""
|
65 |
+
global messages, bot_instance
|
66 |
+
messages = []
|
67 |
+
bot_instance = None
|
68 |
+
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
70 |
+
# Создаем интерфейс чата
|
71 |
+
chat_interface = gr.ChatInterface(
|
72 |
+
fn=predict,
|
73 |
+
title="Чат-бот технической поддержки",
|
74 |
+
description="Задайте ваш вопрос технической поддержке",
|
|
|
75 |
additional_inputs=[
|
76 |
+
gr.Button("Clear", variant="stop")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
],
|
78 |
+
examples=[
|
79 |
+
"Как настроить VPN?",
|
80 |
+
"Почему не работает принтер?",
|
81 |
+
"Как сбросить пароль?"
|
82 |
+
]
|
83 |
)
|
84 |
|
85 |
+
# Добавляем обработчик для кнопки Clear
|
86 |
+
chat_interface.additional_inputs[0].click(
|
87 |
+
fn=clear_chat,
|
88 |
+
inputs=[],
|
89 |
+
outputs=chat_interface.chatbot
|
90 |
+
)
|
91 |
|
92 |
+
# Запускаем приложение
|
93 |
if __name__ == "__main__":
|
94 |
+
chat_interface.launch()
|
b2b/__init__.py
ADDED
File without changes
|
b2b/b2b_prompts.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
ИНСТРУКЦИЯ — Как помочь сотруднику с сервисом B2B
|
3 |
+
|
4 |
+
---
|
5 |
+
|
6 |
+
**1. Узнай, что случилось**
|
7 |
+
|
8 |
+
- Спросить: "Опишите, пожалуйста, с чем именно у вас возникла проблема в сервисе B2B?"
|
9 |
+
(Если сам не рассказал — уточни, что именно пытается сделать: войти в систему, посмотреть договор, создать договор и т.д.)
|
10 |
+
|
11 |
+
---
|
12 |
+
|
13 |
+
**2. Определи тип проблемы**
|
14 |
+
|
15 |
+
- Если сотрудник говорит, что не может зайти в систему или не хватает нужной функции — это проблема с доступом.
|
16 |
+
- Если сотрудник говорит, что система открывается, но не заводится договор, не совпадает сумма/информация, выдает ошибку при работе с договорами — это "не наша проблема".
|
17 |
+
|
18 |
+
---
|
19 |
+
|
20 |
+
**3. Помоги по инструкции**
|
21 |
+
|
22 |
+
**A. Проблема с работой договора (суммы, данные, ошибки в процессе заполнения/оформления договора)**
|
23 |
+
|
24 |
+
- Скажи: "По таким вопросам вам нужно обратиться в чат поддержки BS4osc. Мы помогаем только с доступом в B2B."
|
25 |
+
- Объясни, где найти этот чат или скинь ссылку, если спрашивают.
|
26 |
+
- На этом помощь заканчивается.
|
27 |
+
|
28 |
+
---
|
29 |
+
|
30 |
+
**B. Проблема с доступом (невозможно войти, недоступен нужный функционал, ошибка при входе)**
|
31 |
+
|
32 |
+
1. Уточни, по какой ссылке сотрудник заходит на сайт.
|
33 |
+
2. Проверь, правильно ли вводит логин, пароль.
|
34 |
+
3. Подскажи, где взять нужные данные (если не знает).
|
35 |
+
4. Если не помогает — предложи сбросить пароль:
|
36 |
+
- Запусти функцию для сброса пароля, если умеешь (или подсчитай по инструкции/обратись к старшему).
|
37 |
+
5. Если после сброса пароля не получилось войти или доступа по-прежнему недостаточно:
|
38 |
+
- Скажи: "Скоро с вами свяжется специалист, чтобы помочь с вашей проблемой."
|
39 |
+
- Затем вызови функцию make_jira_task.
|
40 |
+
|
41 |
+
---
|
42 |
+
|
43 |
+
**4. Если не понимаешь, что это за проблема или не уверен, что делать**
|
44 |
+
|
45 |
+
- Спроси у сотрудника дополнительные подробности.
|
46 |
+
- Если все равно не ясно или не можешь помочь — скажи: "Скоро вами займётся специалист."
|
47 |
+
- После этого вызови функцию make_jira_task.
|
48 |
+
|
49 |
+
---
|
50 |
+
|
51 |
+
**5. Завершение общения**
|
52 |
+
|
53 |
+
- Если у сотрудника получилось войти/решить его вопрос — вызови closing_task и скажи: "Спасибо за обращение! Хорошего дня!"
|
54 |
+
- Если не смог помочь — вызови make_jira_task и скажи, что вопрос передан специалисту.
|
55 |
+
|
56 |
+
---
|
57 |
+
|
58 |
+
**ЗАПОМНИ: не нужно предлагать ничего скачивать из интернета, не проси отправлять лишнюю информацию, объясняй только простыми словами ПО ЭТОЙ ИНСТРУКЦИИ!**
|
59 |
+
"""
|
b2b/b2b_service.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from b2b.b2b_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class B2BService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.example_func,
|
20 |
+
]
|
21 |
+
|
22 |
+
@tool
|
23 |
+
def example_func(question: str) -> None:
|
24 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
25 |
+
print("Я example_func")
|
26 |
+
time.sleep(4)
|
27 |
+
logger.info("Кто-то забыл удалить example_func!")
|
28 |
+
|
base_graph.py
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Annotated, Type
|
2 |
+
from langgraph.graph import StateGraph
|
3 |
+
from langchain_core.messages import HumanMessage, ToolMessage
|
4 |
+
from langgraph.graph.message import add_messages
|
5 |
+
from typing_extensions import TypedDict
|
6 |
+
from base_service import BaseService
|
7 |
+
from get_answer_gigachat import AnswerGigaChat
|
8 |
+
import logging
|
9 |
+
import os
|
10 |
+
|
11 |
+
logger = logging.getLogger(__name__)
|
12 |
+
|
13 |
+
class State(TypedDict):
|
14 |
+
messages: Annotated[list, add_messages]
|
15 |
+
|
16 |
+
class BaseGraph:
|
17 |
+
def __init__(self, service: Type[BaseService]):
|
18 |
+
self.service = service
|
19 |
+
self.tools_dict = {tool.name: tool for tool in service.tools}
|
20 |
+
self.llm_with_tools = AnswerGigaChat().bind_tools(service.tools)
|
21 |
+
self.messages = service.get_initial_messages()
|
22 |
+
self.graph = self._build_graph()
|
23 |
+
logger.info(f"BaseGraph with service {service} was built")
|
24 |
+
|
25 |
+
def rebuild_with_new_service(self, service: Type[BaseService]):
|
26 |
+
self.service = service
|
27 |
+
self.tools_dict = {tool.name: tool for tool in service.tools}
|
28 |
+
self.llm_with_tools = AnswerGigaChat().bind_tools(service.tools)
|
29 |
+
self.graph = self._build_graph()
|
30 |
+
self.messages = service.get_messages_from_redirect(self.messages)
|
31 |
+
logger.info(f"BaseGraph was rebuilt with service {service}")
|
32 |
+
|
33 |
+
def _agent_node(self, state: State):
|
34 |
+
try:
|
35 |
+
logger.info("Starting agent_node")
|
36 |
+
messages = state["messages"]
|
37 |
+
response = self.llm_with_tools.invoke(messages)
|
38 |
+
response.content = self._clean_response(response.content)
|
39 |
+
return {"messages": [response]}
|
40 |
+
except Exception as e:
|
41 |
+
logger.error(f"Error in agent_node: {str(e)}", exc_info=True)
|
42 |
+
raise
|
43 |
+
|
44 |
+
def _tool_node(self, state: State):
|
45 |
+
try:
|
46 |
+
logger.info("Starting tool_node")
|
47 |
+
last_message = state["messages"][-1]
|
48 |
+
tool_calls = last_message.tool_calls
|
49 |
+
|
50 |
+
results = []
|
51 |
+
for call in tool_calls:
|
52 |
+
tool_name = call["name"]
|
53 |
+
logger.info(f"Running tool {tool_name}")
|
54 |
+
args = call["args"]
|
55 |
+
|
56 |
+
tool = self.tools_dict.get(tool_name)
|
57 |
+
if not tool:
|
58 |
+
raise ValueError(f"Tool {tool_name} not found")
|
59 |
+
|
60 |
+
tool_response = tool.invoke(args)
|
61 |
+
if tool_name == "make_redirect":
|
62 |
+
self.rebuild_with_new_service(tool_response)
|
63 |
+
results.append(str(tool_response))
|
64 |
+
|
65 |
+
return {"messages": [ToolMessage(content=", ".join(results), tool_call_id=call["id"])]}
|
66 |
+
except Exception as e:
|
67 |
+
logger.error(f"Error in tool_node: {str(e)}", exc_info=True)
|
68 |
+
raise
|
69 |
+
|
70 |
+
def _should_continue(self, state: State):
|
71 |
+
try:
|
72 |
+
logger.info("Checking should continue")
|
73 |
+
last_message = state["messages"][-1]
|
74 |
+
return "tool" if "function_call" in last_message.additional_kwargs else "end"
|
75 |
+
except Exception as e:
|
76 |
+
logger.error(f"Error in should_continue: {str(e)}", exc_info=True)
|
77 |
+
raise
|
78 |
+
|
79 |
+
def _build_graph(self):
|
80 |
+
try:
|
81 |
+
logger.info("Building graph")
|
82 |
+
graph_builder = StateGraph(State)
|
83 |
+
|
84 |
+
graph_builder.add_node("agent", self._agent_node)
|
85 |
+
graph_builder.add_node("tool", self._tool_node)
|
86 |
+
|
87 |
+
graph_builder.add_conditional_edges(
|
88 |
+
"agent",
|
89 |
+
self._should_continue,
|
90 |
+
{"tool": "tool", "end": "__end__"}
|
91 |
+
)
|
92 |
+
graph_builder.add_edge("tool", "agent")
|
93 |
+
graph_builder.set_entry_point("agent")
|
94 |
+
|
95 |
+
return graph_builder.compile()
|
96 |
+
except Exception as e:
|
97 |
+
logger.error(f"Error building graph: {str(e)}", exc_info=True)
|
98 |
+
raise
|
99 |
+
|
100 |
+
def _clean_response(self, content: str) -> str:
|
101 |
+
content = content.replace("</final_answer>", "<final_answer>").replace("</thinking>", "<thinking>")
|
102 |
+
if "<final_answer>" in content:
|
103 |
+
content = content.split("<final_answer>")[1]
|
104 |
+
if "<thinking>" in content:
|
105 |
+
content = content.split("<thinking>")[-1]
|
106 |
+
return content
|
107 |
+
|
108 |
+
def invoke(self, user_input):
|
109 |
+
try:
|
110 |
+
self.messages.append(HumanMessage(content=user_input))
|
111 |
+
result = self.graph.invoke({"messages": self.messages})
|
112 |
+
self.messages = result["messages"]
|
113 |
+
return result
|
114 |
+
except Exception as e:
|
115 |
+
logger.error(f"Error invoking graph: {str(e)}", exc_info=True)
|
116 |
+
raise
|
base_service.py
ADDED
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from abc import ABC, abstractmethod
|
2 |
+
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
|
3 |
+
from system_prompt_template import DIALOG_PROMPT
|
4 |
+
from typing import List
|
5 |
+
from langchain_core.tools import BaseTool, tool
|
6 |
+
from service_by_name import get_service_by_name
|
7 |
+
import logging
|
8 |
+
import time
|
9 |
+
import os
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
load_dotenv()
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
+
|
15 |
+
class BaseService(ABC):
|
16 |
+
def __init__(self):
|
17 |
+
self._base_tools = [self.closing_task,
|
18 |
+
self.make_jira_task,
|
19 |
+
]
|
20 |
+
|
21 |
+
@property
|
22 |
+
@abstractmethod
|
23 |
+
def system_prompt(self) -> str:
|
24 |
+
"""Возвращает системный промпт для сервиса"""
|
25 |
+
pass
|
26 |
+
|
27 |
+
@property
|
28 |
+
@abstractmethod
|
29 |
+
def tools(self) -> List[BaseTool]:
|
30 |
+
"""Возвращает список инструментов сервиса"""
|
31 |
+
pass
|
32 |
+
|
33 |
+
@property
|
34 |
+
def log_path(self) -> str:
|
35 |
+
"""Возвращает путь до папки с логами"""
|
36 |
+
return os.path.join("/tmp", "service.log")
|
37 |
+
|
38 |
+
def get_initial_messages(self):
|
39 |
+
"""Возвращает начальные сообщения для графа"""
|
40 |
+
return [SystemMessage(content=self.system_prompt)]
|
41 |
+
|
42 |
+
def get_messages_from_redirect(self, messages):
|
43 |
+
messages_formatted = ""
|
44 |
+
for message in messages:
|
45 |
+
if isinstance(message, HumanMessage):
|
46 |
+
message += f"user: '{message.content}'\n"
|
47 |
+
elif isinstance(message, AIMessage):
|
48 |
+
message += f"assistant: '{message.content}'\n"
|
49 |
+
|
50 |
+
return [SystemMessage(content=self.system_prompt + DIALOG_PROMPT.format(messages_formatted))]
|
51 |
+
|
52 |
+
@tool
|
53 |
+
def make_redirect(service_name: str):
|
54 |
+
"""Когда оказывается, что пользователю нужен эксперт в другом сервисе,
|
55 |
+
пользователя нужно перенаправить на соответствующего эксперта.
|
56 |
+
Функция принимает аргумент service_name из списка:
|
57 |
+
['openvpn', 'corporate_email', 'internet_access'] и пересоздает класс эксперта
|
58 |
+
с соответствующими новому сервису знаниями."""
|
59 |
+
logger.info("redirecting question to service {service_name}")
|
60 |
+
return get_service_by_name(service_name)
|
61 |
+
|
62 |
+
|
63 |
+
@tool
|
64 |
+
def closing_task(question: str) -> None:
|
65 |
+
"""Задача выполнена, сохраняем результаты"""
|
66 |
+
print("### Задача выполнена, сохраняем результаты")
|
67 |
+
time.sleep(2)
|
68 |
+
logger.info("task closed")
|
69 |
+
|
70 |
+
@tool
|
71 |
+
def make_jira_task(question: str) -> None:
|
72 |
+
"""Задача не может быть выполнена с помощью ии, создаем задачу в jira для того,
|
73 |
+
чтобы её выполнил человек"""
|
74 |
+
time.sleep(2)
|
75 |
+
print("### Скриптом создается задача в jira с соответствующими деталями")
|
76 |
+
logger.info("jira task was make")
|
borlas/__init__.py
ADDED
File without changes
|
borlas/borlas_prompts.py
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
**ИНСТРУКЦИЯ — Как помочь сотруднику решить проблему с сервисом Borlas**
|
3 |
+
|
4 |
+
---
|
5 |
+
|
6 |
+
**1. Определи, что не работает**
|
7 |
+
|
8 |
+
Попроси сотрудника рассказать, что не получается сделать:
|
9 |
+
- Что он хотел сделать?
|
10 |
+
- Какая страница (или окно) открывается?
|
11 |
+
- Есть ли какие-то ошибки/надписи?
|
12 |
+
(Попроси прислать скриншот, если сотрудник может.)
|
13 |
+
|
14 |
+
---
|
15 |
+
|
16 |
+
**2. Действуй по ситуации:**
|
17 |
+
|
18 |
+
---
|
19 |
+
|
20 |
+
**A. Проблема с запуском Borlas через браузер (не открывается плагин, нет начальной страницы, ошибка java)**
|
21 |
+
|
22 |
+
- Спроси, что именно видно на экране:
|
23 |
+
- Если открывается обычная страница браузера (а не окно входа Borlas)
|
24 |
+
- Если пишет ошибку «no Java plugin» или что-то про Java
|
25 |
+
- Вежливо скажи: "Сейчас передам вашу задачу в IT-отдел для проверки."
|
26 |
+
- Вызови функцию make_jira_task.
|
27 |
+
|
28 |
+
---
|
29 |
+
|
30 |
+
**B. Borlas открывается, но не удаётся сделать нужное: проблема с документами или полями (например, не находится договор, поля неправильные, ошибки в работе самой системы)**
|
31 |
+
|
32 |
+
- Уточни, с каким именно документом или действием возникла проблема (номер договора или скрин окна, если можно).
|
33 |
+
- Вежливо скажи: "Сейчас передам вашу задачу специалисту по системе Borlas для исправления."
|
34 |
+
- Вызови функцию make_jira_task.
|
35 |
+
(Если доступна отдельная функция для проектов Borlas, используй её, например make_redirect('borlas_project').)
|
36 |
+
|
37 |
+
---
|
38 |
+
|
39 |
+
**C. Нет доступа или не хватает прав (не открывается вкладка, не подходит пароль, нет нужной функции)**
|
40 |
+
|
41 |
+
- Спроси: "Что именно не видно или не работает? Можете прислать скриншот?"
|
42 |
+
- Если сотрудник не видит нужную вкладку, не может зайти или пишет, что пароль не подходит:
|
43 |
+
- Вежливо скажи: "Сейчас передам вашу заявку, чтобы вам помогли восстановить доступ."
|
44 |
+
- Вызови функцию make_jira_task.
|
45 |
+
|
46 |
+
---
|
47 |
+
|
48 |
+
**3. Если не понимаешь, что за проблема**
|
49 |
+
|
50 |
+
- Попроси сотрудника подробнее описать, что видит на экране или какие действия не получается сделать.
|
51 |
+
- Если всё равно непонятно — вежливо скажи: "Сейчас передам вашу задачу специалисту, который поможет разобраться."
|
52 |
+
- Вызови функцию make_jira_task.
|
53 |
+
|
54 |
+
---
|
55 |
+
|
56 |
+
**4. Завершение общения**
|
57 |
+
|
58 |
+
- Если сотрудник сказал, что проблема решена — вызови closing_task и скажи: "Спасибо за обращение! Хорошего дня!"
|
59 |
+
- Если не можешь помочь — вызови make_jira_task и сообщи, что заявку передали специалисту.
|
60 |
+
|
61 |
+
---
|
62 |
+
|
63 |
+
**ЗАПОМНИ:**
|
64 |
+
- Не проси ничего скачивать или устанавливать самостоятельно!
|
65 |
+
- Не придумывай сам решения — просто узнай ситуацию и следуй этим шагам.
|
66 |
+
- Не пугай сотрудника сложными словами.
|
67 |
+
- Если не уверен — создай задачу на IT-отдел (make_jira_task) и объясни сотруднику, что помощь поступит от специалистов.
|
68 |
+
"""
|
borlas/borlas_service.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from borlas.borlas_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class BorlasService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.example_func,
|
20 |
+
]
|
21 |
+
|
22 |
+
@tool
|
23 |
+
def example_func(question: str) -> None:
|
24 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
25 |
+
print("Я example_func")
|
26 |
+
time.sleep(4)
|
27 |
+
logger.info("Кто-то забыл удалить example_func!")
|
28 |
+
|
broken_cases.txt
ADDED
File without changes
|
corporate_email/__init__.py
ADDED
File without changes
|
corporate_email/corporate_email_prompts.py
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
ИНСТРУКЦИЯ — Как помочь сотруднику решить проблему с корпоративной почтой
|
3 |
+
|
4 |
+
---
|
5 |
+
|
6 |
+
**1. Узнай, что именно не работает**
|
7 |
+
|
8 |
+
- Сначала спроси: "Вы используете почту в браузере или в приложении Outlook?"
|
9 |
+
- Спроси: "Вы находитесь в офисе или работаете из дома?"
|
10 |
+
- Выясни, какая ошибка/что происходит (например: почта не открывается, не приходят письма, не удаётся отправить письмо, есть ли ошибки, какие значки или сообщения видит человек).
|
11 |
+
|
12 |
+
---
|
13 |
+
|
14 |
+
**2. Помоги по инструкции**
|
15 |
+
|
16 |
+
---
|
17 |
+
|
18 |
+
**A. Нет интернета/проблемы с сетью**
|
19 |
+
|
20 |
+
- Спроси: "У вас вообще работает интернет сейчас? Сайты открываются?"
|
21 |
+
- Если человек дома — скажи:
|
22 |
+
"Пожалуйста, подключитесь к интернету, а потом (если требуется доступ к корпоративной почте) — к VPN. После этого попробуйте ещё раз. Всё заработало?"
|
23 |
+
- Если почта открылась — скажи: "Рады помочь! Если что — обращайтесь!"
|
24 |
+
- Если не помогло — переходи к другим пунктам.
|
25 |
+
- Если человек в офисе и у него нет интернета — скажи: "Вижу, что проблема с сетью. Сейчас ваш вопрос передадут специалисту." После этого ВЫЗОВИ make_jira_task.
|
26 |
+
|
27 |
+
---
|
28 |
+
|
29 |
+
**B. Массовый сбой (у многих в офисе не работает почта)**
|
30 |
+
|
31 |
+
- Спроси: "У ваших коллег тоже не открывается или не работает почта?"
|
32 |
+
- Если да — скажи: "Сейчас ваш вопрос передадут специалисту, все сообщения по проблеме будут у него." После этого ВЫЗОВИ make_jira_task.
|
33 |
+
|
34 |
+
---
|
35 |
+
|
36 |
+
**C. Проблема только у одного сотрудника — приложение Outlook зависло или не открывается**
|
37 |
+
|
38 |
+
- Спроси: "Вы используете приложение Outlook? Оно зависло или не открывается?"
|
39 |
+
- Если да — скажи:
|
40 |
+
"Пожалуйста, полностью закройте Outlook и откройте заново. Если не удалось — перезагрузите компьютер."
|
41 |
+
- Если помогло — скажи: "Рады помочь! Хорошего дня!"
|
42 |
+
- Если не помогло — скажи: "Ваш вопрос передан специалисту для дальнейшей помощи." После этого ВЫЗОВИ make_jira_task.
|
43 |
+
|
44 |
+
---
|
45 |
+
|
46 |
+
**D. Почта открывается, но не отправляются или не приходят письма / сообщения об ошибках**
|
47 |
+
|
48 |
+
- Спроси:
|
49 |
+
- "Есть ли сообщение о том, что почтовый ящик переполнен?"
|
50 |
+
- Если да — скажи:
|
51 |
+
"Вам нужно удалить или архивировать старые письма. Если нужна помощь — могу прислать инструкцию, как это сделать."
|
52 |
+
- "Есть ли сообщение об автономном режиме или иконка с красным крестиком/надпись 'Автономная работа'?"
|
53 |
+
- Если да — скажи:
|
54 |
+
"Проверьте, не включён ли режим 'Автономная работа' в Outlook, отключите его."
|
55 |
+
- "Не уходят письма с вложением?"
|
56 |
+
- Если да — скажи:
|
57 |
+
"Для передачи больших файлов создайте запароленный архив. Пароль отправьте отдельным сообщением."
|
58 |
+
- Спроси: "Недавно меняли пароль или доступ?"
|
59 |
+
- Если недавно — скажи: "Попробуйте войти с новым паролем, если не получается — скажите мне."
|
60 |
+
|
61 |
+
- Если после всех советов проблема НЕ решена — скажи:
|
62 |
+
"Сейчас ваш вопрос передадут специалисту, чтобы вам помогли."
|
63 |
+
После этого ВЫЗОВИ make_jira_task.
|
64 |
+
|
65 |
+
---
|
66 |
+
|
67 |
+
**3. Если не знаешь, что делать**
|
68 |
+
|
69 |
+
- Спроси дополнительны�� подробности (что видит, что пишет, когда началось).
|
70 |
+
- Если не понимаешь, что делать — передай задачу старшему специалисту: ВЫЗОВИ make_jira_task, и скажи: "Скоро вам поможет специалист."
|
71 |
+
|
72 |
+
---
|
73 |
+
|
74 |
+
**4. Завершение общения**
|
75 |
+
|
76 |
+
- Если человек сказал, что почта заработала — вызови closing_task и скажи: "Спасибо за обращение! Хорошего дня!"
|
77 |
+
- Если не смог помочь — вызови make_jira_task и скажи: "Ваш вопрос передан специалисту, он с вами свяжется."
|
78 |
+
|
79 |
+
---
|
80 |
+
|
81 |
+
**ЗАПОМНИ:**
|
82 |
+
Не проси устанавливать программы из интернета!
|
83 |
+
Объясняй пошагово и просто. Если что-то не понятно — спрашивай дополнительно, либо вызывай make_jira_task.
|
84 |
+
Не проси делать то, что не написано в этом списке!
|
85 |
+
"""
|
corporate_email/corporate_email_service.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from corporate_email.corporate_email_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class CorporateEmailService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + []
|
19 |
+
|
20 |
+
@tool
|
21 |
+
def check_lock_in_active_dir_and_password(question: str) -> None:
|
22 |
+
"""Проверка блокировки учетной записи сотрудника в системе и разблокировка и проверка на просроченный пароль и обновление пароля. Если эти проблемы вскрываются, то функция их исправляет."""
|
23 |
+
time.sleep(4)
|
24 |
+
logger.info("check_lock_in_active_dir_and_password")
|
crm/__init__.py
ADDED
File without changes
|
crm/crm_prompts.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
|
3 |
+
"""
|
crm/crm_service.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from crm.crm_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class CRMService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.example_func,
|
20 |
+
]
|
21 |
+
|
22 |
+
@tool
|
23 |
+
def example_func(question: str) -> None:
|
24 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
25 |
+
print("Я example_func")
|
26 |
+
time.sleep(4)
|
27 |
+
logger.info("Кто-то забыл удалить example_func!")
|
28 |
+
|
get_answer_gigachat.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.messages import HumanMessage, SystemMessage
|
2 |
+
from langchain_core.runnables.config import RunnableConfig
|
3 |
+
from langchain_gigachat.chat_models import GigaChat
|
4 |
+
import os
|
5 |
+
import re
|
6 |
+
from datetime import datetime
|
7 |
+
from pydantic import Field
|
8 |
+
from dotenv import load_dotenv
|
9 |
+
from typing import Any, Optional
|
10 |
+
import logging
|
11 |
+
from pathlib import Path
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
+
class AnswerGigaChat(GigaChat):
|
15 |
+
THRESHOLD_INPUT_SYMBOLS: int = 40000
|
16 |
+
THRESHOLD_COST: float = 5000.0
|
17 |
+
ERROR_MESSAGE_EXCEEDED_COST: str = "\n\n\n\n\nALERT!!!!!!\nCOST NORM WAS EXCEEDED!!!!!!!!\n{} >= " + str(THRESHOLD_COST) + "\n\n\n\n"
|
18 |
+
COST_PER_INPUT_TOKEN: float = 0.0
|
19 |
+
COST_PER_OUTPUT_TOKEN: float = 1.95e-3
|
20 |
+
LOG_FILE_PATH: str = os.path.join("/tmp", "gigachat.log")
|
21 |
+
|
22 |
+
logger: Any = Field(default=None)
|
23 |
+
|
24 |
+
def __init__(self):
|
25 |
+
super().__init__(credentials="YWZhMjk1OTktNGY3My00ZTNkLTliZDMtMDE2MzU5MzcxNjAyOjc1ZjU4OTJjLTg3MDctNGVjMi04YTVhLTY2NGFlZTQ3MzQ4NQ==", # os.getenv("GIGACHAT_CREDENTIALS"),
|
26 |
+
verify_ssl_certs=False,
|
27 |
+
model="GigaChat-Max",
|
28 |
+
scope="GIGACHAT_API_PERS")
|
29 |
+
self.logger = self._setup_logger(self.LOG_FILE_PATH)
|
30 |
+
|
31 |
+
def _setup_logger(self, log_file: str) -> logging.Logger:
|
32 |
+
logger = logging.getLogger(__name__)
|
33 |
+
logger.setLevel(logging.INFO)
|
34 |
+
|
35 |
+
if not logger.handlers:
|
36 |
+
log_file = Path(log_file)
|
37 |
+
log_file.parent.mkdir(parents=True, exist_ok=True) # Создать папку для логов
|
38 |
+
|
39 |
+
formatter = logging.Formatter(
|
40 |
+
'%(asctime)s - %(name)s - %(levelname)s - '
|
41 |
+
'input_tokens=%(input_tokens)d - output_tokens=%(output_tokens)d - '
|
42 |
+
'cost=%(cost).5f - execution_time=%(execution_time)s - '
|
43 |
+
'status=%(status)s'
|
44 |
+
)
|
45 |
+
|
46 |
+
file_handler = logging.FileHandler(log_file)
|
47 |
+
file_handler.setFormatter(formatter)
|
48 |
+
logger.addHandler(file_handler)
|
49 |
+
|
50 |
+
return logger
|
51 |
+
|
52 |
+
def invoke(self, input: Any, config: Optional[RunnableConfig] = None, **kwargs: Any) -> Any:
|
53 |
+
# Извлекаем сообщения из входных данных
|
54 |
+
if isinstance(input, list):
|
55 |
+
# Если input - это список сообщений (например, [SystemMessage, HumanMessage])
|
56 |
+
messages = input
|
57 |
+
elif isinstance(input, dict) and "messages" in input:
|
58 |
+
# Если input - это dict с ключом "messages" (формат LangGraph)
|
59 |
+
messages = [
|
60 |
+
SystemMessage(content=msg["content"]) if msg["role"] == "system"
|
61 |
+
else HumanMessage(content=msg["content"])
|
62 |
+
for msg in input["messages"]
|
63 |
+
if msg["role"] in ["system", "user"]
|
64 |
+
]
|
65 |
+
elif hasattr(input, "messages"):
|
66 |
+
# Если input - это объект с атрибутом messages
|
67 |
+
messages = input.messages
|
68 |
+
else:
|
69 |
+
# Попробуем интерпретировать input как одно сообщение
|
70 |
+
messages = [HumanMessage(content=str(input))]
|
71 |
+
|
72 |
+
# Проверяем длину ввода
|
73 |
+
system_content = ""
|
74 |
+
user_content = ""
|
75 |
+
for msg in messages:
|
76 |
+
if isinstance(msg, SystemMessage):
|
77 |
+
system_content += msg.content
|
78 |
+
elif isinstance(msg, HumanMessage):
|
79 |
+
user_content += msg.content
|
80 |
+
|
81 |
+
if not self._check_input_length(system_content, user_content):
|
82 |
+
raise ValueError("Too long query")
|
83 |
+
|
84 |
+
# Вызываем родительский метод invoke
|
85 |
+
response = super().invoke(messages, config=config, **kwargs)
|
86 |
+
|
87 |
+
# Логируем информацию о запросе
|
88 |
+
num_input_tokens = response.usage_metadata["input_tokens"]
|
89 |
+
num_output_tokens = response.usage_metadata["output_tokens"]
|
90 |
+
cost = self._calculate_response_cost(num_input_tokens, num_output_tokens)
|
91 |
+
self.logger.info(
|
92 |
+
"got answer",
|
93 |
+
extra={
|
94 |
+
"input_tokens": num_input_tokens,
|
95 |
+
"output_tokens": num_output_tokens,
|
96 |
+
"cost": cost,
|
97 |
+
"execution_time": str(datetime.now()),
|
98 |
+
"status": "success"
|
99 |
+
}
|
100 |
+
)
|
101 |
+
|
102 |
+
# Проверяем общую стоимость
|
103 |
+
total_cost = self._calculate_total_cost()
|
104 |
+
if total_cost >= self.THRESHOLD_COST:
|
105 |
+
error_message = self.ERROR_MESSAGE_EXCEEDED_COST.format(total_cost)
|
106 |
+
print(error_message)
|
107 |
+
response.content = error_message + response.content
|
108 |
+
|
109 |
+
# Возвращаем ответ в соответствующем формате
|
110 |
+
if isinstance(input, dict):
|
111 |
+
return {**input, "messages": [{"role": "assistant", "content": response.content}]}
|
112 |
+
return response
|
113 |
+
|
114 |
+
def _check_input_length(self, system_message: str, user_message: str) -> bool:
|
115 |
+
return len(system_message) + len(user_message) < self.THRESHOLD_INPUT_SYMBOLS
|
116 |
+
|
117 |
+
def _calculate_response_cost(self, num_input_tokens: int, num_output_tokens: int) -> float:
|
118 |
+
return num_input_tokens * self.COST_PER_INPUT_TOKEN + \
|
119 |
+
num_output_tokens * self.COST_PER_OUTPUT_TOKEN
|
120 |
+
|
121 |
+
def _calculate_total_cost(self,
|
122 |
+
start_date: str = '2025-06-01',
|
123 |
+
end_date: str = str(datetime.now().date())):
|
124 |
+
total_cost = 0.0
|
125 |
+
start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
|
126 |
+
end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
|
127 |
+
|
128 |
+
# Регулярное выражение для извлечения даты и cost из строки лога
|
129 |
+
log_pattern = re.compile(
|
130 |
+
r'^(?P<date>\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2},\d{3} - .*? - .*? - '
|
131 |
+
r'input_tokens=\d+ - output_tokens=\d+ - '
|
132 |
+
r'cost=(?P<cost>\d+\.\d{2}) - '
|
133 |
+
r'execution_time=.*? - status=.*$'
|
134 |
+
)
|
135 |
+
|
136 |
+
with open(self.LOG_FILE_PATH, 'r', encoding='utf-8') as file:
|
137 |
+
for line in file:
|
138 |
+
match = log_pattern.match(line)
|
139 |
+
if match:
|
140 |
+
log_date_str = match.group('date')
|
141 |
+
log_date = datetime.strptime(log_date_str, '%Y-%m-%d').date()
|
142 |
+
cost = float(match.group('cost'))
|
143 |
+
|
144 |
+
if start_date <= log_date <= end_date:
|
145 |
+
total_cost += cost
|
146 |
+
|
147 |
+
return total_cost
|
get_classification.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import BaseModel, Field
|
2 |
+
from typing import Literal
|
3 |
+
import logging
|
4 |
+
from langchain.prompts import ChatPromptTemplate
|
5 |
+
from langchain_core.output_parsers import JsonOutputParser
|
6 |
+
from get_answer_gigachat import AnswerGigaChat
|
7 |
+
from service_by_name import get_service_by_name
|
8 |
+
from base_graph import BaseGraph
|
9 |
+
import os
|
10 |
+
from dotenv import load_dotenv
|
11 |
+
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
+
log_path = os.path.join("/tmp", "service.log")
|
15 |
+
if os.path.exists(log_path):
|
16 |
+
os.remove(log_path)
|
17 |
+
logging.basicConfig(
|
18 |
+
level=logging.INFO,
|
19 |
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
20 |
+
handlers=[
|
21 |
+
logging.FileHandler(log_path)
|
22 |
+
]
|
23 |
+
)
|
24 |
+
logger = logging.getLogger(__name__)
|
25 |
+
|
26 |
+
|
27 |
+
class SupportCategory(BaseModel):
|
28 |
+
"""Структура для классификации вопроса техподдержки."""
|
29 |
+
category: Literal["openvpn", "corporate_email", "internet_access", "другое"] = Field(
|
30 |
+
description="Категория вопроса: openvpn, почта, wifi или другое"
|
31 |
+
)
|
32 |
+
confidence: float = Field(
|
33 |
+
description="Уверенность модели в классификации (0-1)",
|
34 |
+
ge=0,
|
35 |
+
le=1
|
36 |
+
)
|
37 |
+
|
38 |
+
llm = AnswerGigaChat()
|
39 |
+
parser = JsonOutputParser(pydantic_object=SupportCategory)
|
40 |
+
|
41 |
+
prompt = ChatPromptTemplate.from_template("""
|
42 |
+
Ты — вдумчивый и ответственный специалист технической поддержки.
|
43 |
+
Под твоим руководством работают эксперты - работники технической поддержки, каждый из которых
|
44 |
+
разбирается в своем отдельном сервисе. Ты анализируешь поступающие вопросы и распределяешь их по экспертам.
|
45 |
+
Тебе нужно понять, к какому сервису относится проблема пользователя и направить его к соответствующему эксперту.
|
46 |
+
Тебе не нужно уточнять у пользователя детали, чтобы точнее определить категорию задачи,
|
47 |
+
ведь, если ты ошибешься, эксперт в ходе диалога это поймет
|
48 |
+
и перенаправит запрос на другого специалиста.
|
49 |
+
|
50 |
+
**Категории:**
|
51 |
+
- "openvpn" — если вопрос про VPN, подключение к корпоративной сети, сертификаты OpenVPN.
|
52 |
+
- "corporate_email" — вопрос отправляется к эксперту, который помогает с
|
53 |
+
корпоративной почтой сотрудников компании, которая доступна по адресу mail-13.renlife.com
|
54 |
+
и поддерживает работу через веб-интерфейс в браузере или через клиент Microsoft Outlook.
|
55 |
+
- "internet_access" — вопрос отправляется к эксперту, который помогает с вопросами про
|
56 |
+
доступ в интернет, в том числе доступ к корпоративным ресурсам на корпоративной технике(пк, нб).
|
57 |
+
- "b2b" - ПО, обеспечивающее взаимодействие наших сотрудников с партнерами компании и наоборот, является web-формой общения с БД Borlas. В ПО реализована возможность заведения и просмотра уже созданных ДС.
|
58 |
+
- "crm" - ПО, которое позволяет управлять продажами и сотрудниками, проводить встречи и звонки. В системе предусмотрена возможность записи встреч и разговоров. Система работает в синергии с телефонией Naumen.
|
59 |
+
- "one_c" - 1С - Инструмент для автоматизации учета и управления бизнесом. Документооборот, бухгалтерия, кадровый учет, учет техники и оборудования.
|
60 |
+
- "borlas" - База данных клиентов, сотрудников, ДС.
|
61 |
+
- "print_and_scan" - Многофункциональное устройство (сокр. МФУ) — устройство, совмещающее в себе функции принтера, сканера, копировального аппарата, иногда также факса и терминала электронной почты, поточные сканеры
|
62 |
+
- "другое" — если вопрос не подходит ни под одну категорию.
|
63 |
+
|
64 |
+
Верни ответ в формате JSON с полями `category` и `confidence`.
|
65 |
+
|
66 |
+
**Вопрос пользователя:** {question}
|
67 |
+
""")
|
68 |
+
|
69 |
+
classification_chain = prompt | llm | parser
|
70 |
+
|
71 |
+
|
72 |
+
def get_graph_class(question):
|
73 |
+
result = classification_chain.invoke({"question": question})
|
74 |
+
service_name = result["category"]
|
75 |
+
confidence = result["confidence"]
|
76 |
+
logger.info(f"question: {question} goes to {service_name} with confidence {confidence}")
|
77 |
+
service = BaseGraph(get_service_by_name(service_name))
|
78 |
+
return service
|
internet_access/__init__.py
ADDED
File without changes
|
internet_access/internet_access_cases.json
ADDED
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"advice": "",
|
3 |
+
"problems": [
|
4 |
+
{
|
5 |
+
"id": 1,
|
6 |
+
"case": "Проблема с оборудованием в офисе сразу у нескольких пользователей",
|
7 |
+
"description": "Во всем офисе наблюдаются проблемы с интернетом",
|
8 |
+
"symptoms": [
|
9 |
+
"Сетевое оборудование(маршрутизатор, точка доступа) отключено или лампочки горят необычно",
|
10 |
+
"У пользователей и его коллег сеть есть, но доступ в интернет отсутствует"
|
11 |
+
],
|
12 |
+
"solution": [
|
13 |
+
{
|
14 |
+
"step": 1,
|
15 |
+
"action": "Спроси у пользователя, есть ли индикации на точке доступа и маршрутизаторе"
|
16 |
+
},
|
17 |
+
{
|
18 |
+
"step": 2,
|
19 |
+
"condition": "Индикация есть",
|
20 |
+
"action": "Попроси описать, что и как часто мигает",
|
21 |
+
"next_step": {
|
22 |
+
"action": "Создай задачу в Jira, сообщи, что скоро с пользователем свяжется сотрудник и поможет",
|
23 |
+
"function": "make_jira_task"
|
24 |
+
}
|
25 |
+
},
|
26 |
+
{
|
27 |
+
"step": 3,
|
28 |
+
"condition": "Индикации нет",
|
29 |
+
"action": "Попроси у пользователя перезагрузить оборудование"
|
30 |
+
},
|
31 |
+
{
|
32 |
+
"step": 4,
|
33 |
+
"condition": "Перезагрузка не помогла",
|
34 |
+
"action": "Создай задачу в Jira, сообщи, что скоро с пользователем свяжется сотрудник и поможет",
|
35 |
+
"function": "make_jira_task"
|
36 |
+
}
|
37 |
+
]
|
38 |
+
},
|
39 |
+
{
|
40 |
+
"id": 2,
|
41 |
+
"case": "Единичная проблема с интернетом в офисе (только у самого пользователя)",
|
42 |
+
"description": "Только у одного пользователя не работает интернет, у коллег все в порядке",
|
43 |
+
"symptoms": [
|
44 |
+
"Нет доступа в интернет только на одном ПК",
|
45 |
+
"Корпоративные ресурсы недоступны только на одном ПК",
|
46 |
+
"Интернет подключен, но пользователь сообщает об отсутствии доступа"
|
47 |
+
],
|
48 |
+
"solution": [
|
49 |
+
{
|
50 |
+
"step": 1,
|
51 |
+
"action": "Предложи сбросить сеть, если нужно, объясни, как это сделать"
|
52 |
+
},
|
53 |
+
{
|
54 |
+
"step": 2,
|
55 |
+
"action": "Предложи перезагрузить компьютер"
|
56 |
+
},
|
57 |
+
{
|
58 |
+
"step": 3,
|
59 |
+
"condition": "Проблема сохраняется",
|
60 |
+
"action": "Создай задачу в Jira, сообщи, что скоро с пользователем свяжется сотрудник и поможет",
|
61 |
+
"function": "make_jira_task"
|
62 |
+
}
|
63 |
+
]
|
64 |
+
},
|
65 |
+
{
|
66 |
+
"id": 3,
|
67 |
+
"case": "Проблема дома",
|
68 |
+
"description": "пользователь работает дома и у него проблемы с WiFi",
|
69 |
+
"symptoms": [
|
70 |
+
"Телефон подключается к сети, но ПК не подключается",
|
71 |
+
"Домашняя сеть не отображается в списке доступных сетей на ПК",
|
72 |
+
"Подключение есть, но доступа в интернет нет"
|
73 |
+
],
|
74 |
+
"solution": [
|
75 |
+
{
|
76 |
+
"step": 1,
|
77 |
+
"action": "Уточни, есть ли интернет в доме вообще (подключается ли телефон)"
|
78 |
+
},
|
79 |
+
{
|
80 |
+
"step": 2,
|
81 |
+
"condition": "Интернет в доме отсутствует",
|
82 |
+
"action": "Порекомендуй обратиться к провайдеру или проверить роутер",
|
83 |
+
"note": "Обращение регистрировать не нужно"
|
84 |
+
},
|
85 |
+
{
|
86 |
+
"step": 3,
|
87 |
+
"condition": "Телефон подключается к сети",
|
88 |
+
"action": "Предложи сбросить сеть, если нужно, объясни, как это сделать"
|
89 |
+
},
|
90 |
+
{
|
91 |
+
"step": 4,
|
92 |
+
"action": "Предложить перезагрузить компьютер"
|
93 |
+
},
|
94 |
+
{
|
95 |
+
"step": 5,
|
96 |
+
"condition": "Проблема сохраняется",
|
97 |
+
"action": "Создай задачу в Jira, сообщи, что скоро с пользователем свяжется сотрудник и поможет",
|
98 |
+
"function": "make_jira_task"
|
99 |
+
}
|
100 |
+
]
|
101 |
+
},
|
102 |
+
{
|
103 |
+
"id": 4,
|
104 |
+
"case": "Проблемы с VPN",
|
105 |
+
"description": "Недоступны только корпоративные ресурсы, но общий доступ в интернет есть",
|
106 |
+
"symptoms": [
|
107 |
+
"Сайты не относящиеся к корпоративным открываются",
|
108 |
+
"Корпоративные сайты (jira, my и т.д.) не открываются"
|
109 |
+
],
|
110 |
+
"solution": [
|
111 |
+
{
|
112 |
+
"action": "Перенаправить на сервис openvpn, вызывав функцию make_redirect и передав в неё название сервиса для 'openvpn'",
|
113 |
+
"function": "make_redirect"
|
114 |
+
}
|
115 |
+
]
|
116 |
+
}
|
117 |
+
],
|
118 |
+
"system_functions": {
|
119 |
+
"make_jira_task": {
|
120 |
+
"description": "Создание тикета в Jira для IT-отдела",
|
121 |
+
"required_params": []
|
122 |
+
}
|
123 |
+
}
|
124 |
+
}
|
internet_access/internet_access_prompts.py
ADDED
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
ИНСТРУКЦИЯ — Как помочь сотруднику решить проблему с интернетом (Wi-Fi)
|
3 |
+
|
4 |
+
---
|
5 |
+
|
6 |
+
**1. Узнай, где находится человек**
|
7 |
+
|
8 |
+
- Сначала спроси: "Вы сейчас находитесь в офисе или дома?"
|
9 |
+
(Если человек сам это не сказал.)
|
10 |
+
|
11 |
+
---
|
12 |
+
|
13 |
+
**2. Определи, что случилось**
|
14 |
+
|
15 |
+
- Если в офисе — спроси, у всех или только у него не работает интернет.
|
16 |
+
- Если дома — спроси, есть ли интернет на телефоне/других устройствах.
|
17 |
+
|
18 |
+
---
|
19 |
+
|
20 |
+
**3. Помоги по инструкции**
|
21 |
+
|
22 |
+
**A. Офис — у многих интернет не работает**
|
23 |
+
- Спроси: "Горят ли лампочки на коробке, которая раздаёт интернет (роутер/точка доступа)?"
|
24 |
+
- Если лампочки мигают — спроси, как именно мигают. Потом скажи: "Скоро с вами свяжется специалист для помощи." После этого вызови функцию make_jira_task.
|
25 |
+
- Если лампочки вообще не горят — попроси перезагрузить роутер (выключи — включи).
|
26 |
+
- Если не помогло — скажи: "Скоро с вами свяжется специалист для помощи." После этого вызови функцию make_jira_task.
|
27 |
+
|
28 |
+
**B. Офис — только у одного нет интернета**
|
29 |
+
- Посоветуй сбросить сеть на компьютере (помоги, если нужно, объяснить это).
|
30 |
+
- Если не помогло — попроси перезагрузить компьютер.
|
31 |
+
- Если не помогло — скажи: "Скоро с вами свяжется специалист для помощи." После этого вызови функцию make_jira_task.
|
32 |
+
|
33 |
+
**C. Дом — не работает интернет или Wi-Fi**
|
34 |
+
- Спросить: "Интернет вообще есть в доме? Работает ли на телефоне/планшете?"
|
35 |
+
- Если нигде не работает — посоветуй перезагрузить домашний роутер или обратиться к своему провайдеру. Задачу создавать НЕ нужно.
|
36 |
+
- Если только на компьютере не работает, но на других устройствах есть:
|
37 |
+
- Посоветуй сбросить сеть на компьютере (объясни, если не понимает).
|
38 |
+
- Если не помогает — попроси перезагрузить компьютер.
|
39 |
+
- Если не помогает — скажи: "Скоро с вами свяжется специалист для помощи." После этого вызови функцию make_jira_task.
|
40 |
+
|
41 |
+
**D. Есть интернет, но не работают только рабочие сайты (корпоративные ресурсы: jira, my и т.д.)**
|
42 |
+
- Направь задачу другому специалисту: вызови make_redirect('openvpn'). Скажи: "Сейчас вас перенаправят к специалисту по VPN, он поможет."
|
43 |
+
|
44 |
+
---
|
45 |
+
|
46 |
+
**4. Если не знаешь, что делать**
|
47 |
+
- Спроси у сотрудника дополнительные подробности.
|
48 |
+
- Если не понимаешь, что это за проблема — вызови make_jira_task и скажи: "Скоро с вами свяжется специалист для помощи."
|
49 |
+
|
50 |
+
---
|
51 |
+
|
52 |
+
**5. Завершение общения**
|
53 |
+
- Если человек сказал, что у него всё получилось — вызови closing_task и скажи: "Спасибо за обращение! Хорошего дня!"
|
54 |
+
- Если не можешь помочь или это не твоя задача — вызови make_jira_task или make_redirect, и скажи, что его вопрос передан специалисту.
|
55 |
+
|
56 |
+
---
|
57 |
+
|
58 |
+
**ЗАПОМНИ: не нужно предлагать ничего качать из интернета, не нужно "переспрашивать" по нескольку раз, не усложняй объяснения — спрашивай и объясняй просто и только по этому списку!**
|
59 |
+
"""
|
internet_access/internet_access_service.py
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from internet_access.internet_access_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
class InternetAccessService(BaseService):
|
11 |
+
@property
|
12 |
+
def system_prompt(self) -> str:
|
13 |
+
return SYSTEM_PROMPT
|
14 |
+
|
15 |
+
@property
|
16 |
+
def tools(self) -> List[BaseTool]:
|
17 |
+
return self._base_tools + [
|
18 |
+
self.example_func,
|
19 |
+
]
|
20 |
+
|
21 |
+
@tool
|
22 |
+
def example_func(question: str) -> None:
|
23 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
24 |
+
print("Я example_func")
|
25 |
+
time.sleep(4)
|
26 |
+
logger.info("Кто-то забыл удалить example_func!")
|
27 |
+
|
one_c/__init__.py
ADDED
File without changes
|
one_c/one_c_prompts.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
ИНСТРУКЦИЯ — Как помочь сотруднику с проблемами в 1С
|
3 |
+
|
4 |
+
---
|
5 |
+
|
6 |
+
**1. Уточни, что не так**
|
7 |
+
|
8 |
+
Спросить:
|
9 |
+
"В чем именно у вас проблема с 1С? Что не открывается или какая ошибка появляется?"
|
10 |
+
(Если сам сотрудник не уточнил).
|
11 |
+
|
12 |
+
---
|
13 |
+
|
14 |
+
**2. Определи тип ситуации по подсказкам**
|
15 |
+
|
16 |
+
- Если 1С не запускается, сильно тормозит, или что‑то странно выглядит (нет нужных окон/виджетов, ошибка о версии) — это проблема с самой программой.
|
17 |
+
- Если пишет про недостаток прав, требует логин/пароль, не дает принять или согласовать задачу, не видны задачи, пишет про лицензию или отказ в доступе — это проблема с доступом/правами.
|
18 |
+
- Если ошибка или проблема только с одной конкретной задачей или документом (например, в документе выбрался не тот человек для подписи, не назначается задача) — это проблема с конкретной задачей в 1С.
|
19 |
+
|
20 |
+
---
|
21 |
+
|
22 |
+
**3. Дейcтвуй по инструкции**
|
23 |
+
|
24 |
+
**A. Программа 1С тормозит, странно работает, не грузится, ошибки про разные версии**
|
25 |
+
- Скажи: "Я сейчас оформлю обращение, специалист свяжется с вами и поможет решить проблему."
|
26 |
+
- Вызови функцию make_jira_task.
|
27 |
+
|
28 |
+
**B. Проблемы с доступом, правами, лицензиями, не видно задач**
|
29 |
+
- Скажи: "Я сейчас передам ваш вопрос в отдел 1С, специалист свяжется с вами и поможет."
|
30 |
+
- Вызови функцию make_jira_task с переводом на проект 1C.
|
31 |
+
|
32 |
+
**C. Проблема с одной конкретной задачей, документом или подписанием**
|
33 |
+
- Скажи: "Вашу проблему передам специалисту по 1С, он поможет разобраться."
|
34 |
+
- Вызови функцию make_jira_task с переводом на проект 1C.
|
35 |
+
|
36 |
+
---
|
37 |
+
|
38 |
+
**4. Если не понимаешь, что это за проблема или сотрудник описывает что‑то непонятное**
|
39 |
+
- Попроси подробнее описать ошибку или что не получается.
|
40 |
+
- Если всё равно не понятно — вызови make_jira_task и скажи: "Я передал обращение специалисту, он поможет вам."
|
41 |
+
|
42 |
+
---
|
43 |
+
|
44 |
+
**5. Завершение общения**
|
45 |
+
- Если сотрудник сказал, что у него всё получилось — вызови closing_task и скажи: "Спасибо за обращение! Хорошего дня!"
|
46 |
+
- Если ты не можешь помочь — вызови make_jira_task и скажи, что вопрос передан специалисту.
|
47 |
+
|
48 |
+
---
|
49 |
+
|
50 |
+
**Главное: не пытайся сам настраивать 1С или менять права, не проси переустанавливать программы, не усложняй объяснения — просто следуй этим простым шагам!**
|
51 |
+
"""
|
one_c/one_c_service.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from one_c.one_c_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class OneCService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.example_func,
|
20 |
+
]
|
21 |
+
|
22 |
+
@tool
|
23 |
+
def example_func(question: str) -> None:
|
24 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
25 |
+
print("Я example_func")
|
26 |
+
time.sleep(4)
|
27 |
+
logger.info("Кто-то забыл удалить example_func!")
|
28 |
+
|
openvpn/__init__.py
ADDED
File without changes
|
openvpn/openvpn_prompts.py
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from system_prompt_template import SYSTEM_PROMPT_TEMPLATE
|
2 |
+
|
3 |
+
service_name = "open vpn"
|
4 |
+
|
5 |
+
service_description = ""
|
6 |
+
|
7 |
+
cases = """
|
8 |
+
У пользователя может быть одна из 3 ситуаций, надо определить, какая у него:
|
9 |
+
|
10 |
+
- Консультация.
|
11 |
+
Это когда:
|
12 |
+
- Программа есть
|
13 |
+
- Программа открывается
|
14 |
+
- vpn подключается
|
15 |
+
В этом случае надо предпринять действия:
|
16 |
+
- Проконсультировать пользователя, как пользоваться vpn.
|
17 |
+
и ответить на его вопросы.
|
18 |
+
- Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение.
|
19 |
+
в техническую поддержку
|
20 |
+
- Если ты не можешь помочь пользователю или он задает вопросы, на которые у тебя нет инструкций,
|
21 |
+
вызови функцию make_jira_task, сообщи, что была создана задача в jira и в ближайшее времся с
|
22 |
+
пользователем свяжется сотрудник технической поддержки.
|
23 |
+
|
24 |
+
- Установка
|
25 |
+
Это когда:
|
26 |
+
- Программы нет на компьютере пользователя.
|
27 |
+
В этом случае надо предпринять действия:
|
28 |
+
- Вызывать функцию install_program
|
29 |
+
- Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение
|
30 |
+
в техническую поддержку
|
31 |
+
- Если пользователь говорит, что vpn по-прежнему не работает, вызови функцию script_part
|
32 |
+
|
33 |
+
- Переустановка
|
34 |
+
Это когда:
|
35 |
+
- Программы не открывается.
|
36 |
+
В этом случае надо предпринять действия:
|
37 |
+
- Вызывать функцию delete_and_install_program
|
38 |
+
- Если пользователь подтвердил, что все работает, вызови функцию closing_task, поблагодари за обращение
|
39 |
+
в техническую поддержку
|
40 |
+
- Если пользователь говорит, что vpn по-прежнему не работает, вызови функцию script_part
|
41 |
+
"""
|
42 |
+
|
43 |
+
|
44 |
+
broken_cases = """
|
45 |
+
'В таком случае вам необходимо установить программу OpenVPN. Для этого выполните следующие действия:
|
46 |
+
|
47 |
+
Перейдите на официальный сайт OpenVPN (https://openvpn.net/) и скачайте последнюю версию программы.
|
48 |
+
Запустите установочный файл и следуйте инструкциям мастера установки.
|
49 |
+
После завершения установки запустите программу и попробуйте подключить VPN.
|
50 |
+
Если возникнут трудности при установке или использовании программы, пожалуйста, дайте мне знать.' - этот ответ нарушает правила 8 и 9
|
51 |
+
|
52 |
+
'Тогда, возможно, проблема связана с настройками самого приложения или сервера.
|
53 |
+
В этом случае я рекомендую обратиться к вашему системному администратору или в службу поддержки вашего VPN-провайдера.
|
54 |
+
Они смогут провести более детальную диагностику и устранить проблему.' - этот ответ нарушает правила 8 и 12
|
55 |
+
"""
|
56 |
+
|
57 |
+
|
58 |
+
knowledge = """
|
59 |
+
Инструкции:
|
60 |
+
Как пользоваться openvpn:
|
61 |
+
Документ представляет собой инструкцию по подключению к корпоративной сети через VPN,
|
62 |
+
используя приложение OpenVPN Connect. Он предназначен для сотрудников компании,
|
63 |
+
которым необходимо получить удалённый доступ к внутренним ресурсам из внешней сети
|
64 |
+
(например, при работе из дома или вне офиса).
|
65 |
+
|
66 |
+
Ключевые шаги подключения к VPN:
|
67 |
+
|
68 |
+
Подключение к внешней сети:
|
69 |
+
Убедитесь, что устройство подключено к любой внешней сети: через Ethernet, Wi-Fi или мобильную точку доступа.
|
70 |
+
Статус подключения можно проверить через иконку интернета в нижнем правом углу экрана.
|
71 |
+
Запуск OpenVPN Connect:
|
72 |
+
Найдите и запустите приложение OpenVPN Connect.
|
73 |
+
Оно может находиться на рабочем столе или его можно найти через строку поиска Windows, введя «OpenVPN Connect».
|
74 |
+
Активация VPN-соединения:
|
75 |
+
Переключите ползунок подключения в приложении, чтобы инициировать подключение к VPN.
|
76 |
+
В появившемся окне введите свои учётные данные (логин и пароль).
|
77 |
+
Нажмите кнопку «ОК» для подтверждения.
|
78 |
+
Готово:
|
79 |
+
После авторизации будет установлен удалённый доступ к сетевым ресурсам компании.
|
80 |
+
Цель документа:
|
81 |
+
Обеспечить безопасный доступ сотрудников к корпоративным системам из внешней интернет-сети посредством OpenVPN.
|
82 |
+
|
83 |
+
Требования:
|
84 |
+
Установленное приложение OpenVPN Connect.
|
85 |
+
Доступ к внешнему интернету.
|
86 |
+
Действующие учётные данные сотрудника.
|
87 |
+
|
88 |
+
Как проверить, установлена ли программа open vpn:
|
89 |
+
В поиске набрать OpenVPN. Если результаты поиска отсутствуют, то программа не установлена.
|
90 |
+
|
91 |
+
|
92 |
+
Как проверить, подключился ли open vpn:
|
93 |
+
Успех подключения выглядит как на скриншоте, должна появиться надпись CONNECTED.
|
94 |
+
|
95 |
+
Как правильно вводить учетные данные:
|
96 |
+
Логин вводится в формате Имя.Фамилия на латинице с заглавных букв. Пример: Ivan.Ivanov. Пароль вводится такой же, как при входе в пк.
|
97 |
+
"""
|
98 |
+
|
99 |
+
SYSTEM_PROMPT = SYSTEM_PROMPT_TEMPLATE.format(service_name=service_name,
|
100 |
+
service_description=service_description,
|
101 |
+
cases=cases,
|
102 |
+
broken_cases=broken_cases,
|
103 |
+
knowledge=knowledge)
|
openvpn/openvpn_service.py
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from openvpn.openvpn_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class OpenVPNService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.script_part,
|
20 |
+
self.install_program,
|
21 |
+
self.delete_and_install_program
|
22 |
+
]
|
23 |
+
|
24 |
+
@tool
|
25 |
+
def script_part(question: str) -> None:
|
26 |
+
"""Запускаем большой скрипт для проверки возможных проблем с приложением
|
27 |
+
и их устранением"""
|
28 |
+
print("ran_graph(question)")
|
29 |
+
time.sleep(4)
|
30 |
+
logger.info("script part")
|
31 |
+
|
32 |
+
@tool
|
33 |
+
def install_program(question: str) -> None:
|
34 |
+
"""Запускаем скрипты, которые ставят программу openvpn"""
|
35 |
+
print("### Скриптом устанавливаем программу")
|
36 |
+
time.sleep(3)
|
37 |
+
logger.info("installing program")
|
38 |
+
|
39 |
+
@tool
|
40 |
+
def delete_and_install_program(question: str) -> None:
|
41 |
+
"""Запускаем скрипты, которые удаляют и ставят программу openvpn"""
|
42 |
+
print("### Скриптом удаляем и ставим программу")
|
43 |
+
time.sleep(3)
|
44 |
+
logger.info("deleting and installing program")
|
print_and_scan/__init__.py
ADDED
File without changes
|
print_and_scan/print_and_scan_prompts.py
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
ИНСТРУКЦИЯ — Как помочь сотруднику с МФУ (печать/сканирование)
|
3 |
+
|
4 |
+
---
|
5 |
+
|
6 |
+
**1. Узнай, в чём проблема**
|
7 |
+
|
8 |
+
- Спроси: "Что именно не работает — печать, сканирование или копирование?"
|
9 |
+
- Пусть сотрудник опишет саму проблему (например, появляется ошибка, странный шум, застряла бумага, не печатает вообще и т.д.).
|
10 |
+
|
11 |
+
---
|
12 |
+
|
13 |
+
**2. Определи тип проблемы**
|
14 |
+
|
15 |
+
### **A. На устройстве мигает ошибка или пишет что-то странное, издаёт необычный звук, застряла бумага, не захватывается бумага, печатает блекло или с полосами**
|
16 |
+
|
17 |
+
- Спроси: "Какая ошибка пишет на экране аппарата?" (например: «нет тонера», «замените картридж», «застряла бумага» и т.д.).
|
18 |
+
- Если пишет о том, что надо заменить картридж или тонер — спроси, есть ли запасной картридж.
|
19 |
+
- Если есть — объясни, как поменять картридж (если не знает — инструкция обычно на картридже или устройстве, помоги найти).
|
20 |
+
- Если картриджа нет или не помогла замена — скажи: "Скоро с вами свяжется специалист для помощи." После этого вызови функцию **make_jira_task**.
|
21 |
+
- Если ошибку не понятно как исправить, застряла бумага и сотрудник не может её вытащить, или аппарат издаёт необычные/громкие звуки — скажи: "Скоро с вами свяжется специалист для помощи." После этого вызови **make_jira_task**.
|
22 |
+
|
23 |
+
---
|
24 |
+
|
25 |
+
### **B. Всё включено, ошибок на экране нет, но нельзя напечатать или отсканировать с компьютера**
|
26 |
+
|
27 |
+
- Спроси: "Документ уходит на печать и зависает в очереди или никак не появляется?"
|
28 |
+
- Спроси: "Компьютер видит МФУ вообще? (доступен принтер/сканер в списке устройств)"
|
29 |
+
- Если не удаётся напечатать или нет связи со сканером — скажи:
|
30 |
+
"Сейчас с вами свяжется специалист и поможет всё настроить." После этого вызови **make_jira_task**.
|
31 |
+
|
32 |
+
---
|
33 |
+
|
34 |
+
**3. Если не знаешь, что делать**
|
35 |
+
|
36 |
+
- Спроси у сотрудника подробности, что пишет/как ведёт себя МФУ.
|
37 |
+
- Если всё равно не можешь понять, что происходит — скажи: "Скоро с вами свяжется специалист для помощи." и вызови **make_jira_task**.
|
38 |
+
|
39 |
+
---
|
40 |
+
|
41 |
+
**4. Завершение общения**
|
42 |
+
|
43 |
+
- Если человек сказал, что всё получилось, всё заработало — вызови **closing_task** и скажи: "Спасибо за обращение! Хорошего дня!"
|
44 |
+
- Если не можешь помочь или это не твоя задача — вызови **make_jira_task** и скажи, что его вопрос передан специалисту.
|
45 |
+
|
46 |
+
---
|
47 |
+
|
48 |
+
**ЗАПОМНИ:**
|
49 |
+
- Ничего не предлагай скачать из интернета!
|
50 |
+
- Не усложняй объяснения и ничего не "ковыряй" на компьютере пользователя — просто следуй пунктам инструкции!
|
51 |
+
- При любых сомнениях — сразу обращайся к старшим: **make_jira_task**!
|
52 |
+
"""
|
print_and_scan/print_and_scan_service.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from print_and_scan.print_and_scan_prompts import SYSTEM_PROMPT
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class PrintAndScanService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.example_func,
|
20 |
+
]
|
21 |
+
|
22 |
+
@tool
|
23 |
+
def example_func(question: str) -> None:
|
24 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
25 |
+
print("Я example_func")
|
26 |
+
time.sleep(4)
|
27 |
+
logger.info("Кто-то забыл удалить example_func!")
|
28 |
+
|
requirements.txt
CHANGED
@@ -1 +1,68 @@
|
|
1 |
-
huggingface_hub==0.25.2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
huggingface_hub==0.25.2
|
2 |
+
altair==5.5.0
|
3 |
+
annotated-types==0.7.0
|
4 |
+
anyio==4.9.0
|
5 |
+
attrs==25.3.0
|
6 |
+
blinker==1.9.0
|
7 |
+
cachetools==6.1.0
|
8 |
+
certifi==2025.6.15
|
9 |
+
charset-normalizer==3.4.2
|
10 |
+
click==8.2.1
|
11 |
+
dotenv==0.9.9
|
12 |
+
gigachat==0.1.40
|
13 |
+
gitdb==4.0.12
|
14 |
+
GitPython==3.1.44
|
15 |
+
h11==0.16.0
|
16 |
+
httpcore==1.0.9
|
17 |
+
httpx==0.28.1
|
18 |
+
idna==3.10
|
19 |
+
Jinja2==3.1.6
|
20 |
+
jsonpatch==1.33
|
21 |
+
jsonpointer==3.0.0
|
22 |
+
jsonschema==4.24.0
|
23 |
+
jsonschema-specifications==2025.4.1
|
24 |
+
langchain==0.3.26
|
25 |
+
langchain-core==0.3.67
|
26 |
+
langchain-gigachat==0.3.11
|
27 |
+
langchain-text-splitters==0.3.8
|
28 |
+
langgraph==0.5.0
|
29 |
+
langgraph-checkpoint==2.1.0
|
30 |
+
langgraph-prebuilt==0.5.1
|
31 |
+
langgraph-sdk==0.1.72
|
32 |
+
langsmith==0.4.4
|
33 |
+
MarkupSafe==3.0.2
|
34 |
+
narwhals==1.44.0
|
35 |
+
numpy==2.3.1
|
36 |
+
orjson==3.10.18
|
37 |
+
ormsgpack==1.10.0
|
38 |
+
packaging==24.2
|
39 |
+
pandas==2.3.0
|
40 |
+
pillow==11.2.1
|
41 |
+
protobuf==6.31.1
|
42 |
+
pyarrow==20.0.0
|
43 |
+
pydantic==2.11.7
|
44 |
+
pydantic_core==2.33.2
|
45 |
+
pydeck==0.9.1
|
46 |
+
python-dateutil==2.9.0.post0
|
47 |
+
python-dotenv==1.1.1
|
48 |
+
pytz==2025.2
|
49 |
+
PyYAML==6.0.2
|
50 |
+
referencing==0.36.2
|
51 |
+
requests==2.32.4
|
52 |
+
requests-toolbelt==1.0.0
|
53 |
+
rpds-py==0.25.1
|
54 |
+
six==1.17.0
|
55 |
+
smmap==5.0.2
|
56 |
+
sniffio==1.3.1
|
57 |
+
SQLAlchemy==2.0.41
|
58 |
+
streamlit==1.46.1
|
59 |
+
tenacity==9.1.2
|
60 |
+
toml==0.10.2
|
61 |
+
tornado==6.5.1
|
62 |
+
types-requests==2.32.4.20250611
|
63 |
+
typing-inspection==0.4.1
|
64 |
+
typing_extensions==4.14.0
|
65 |
+
tzdata==2025.2
|
66 |
+
urllib3==2.5.0
|
67 |
+
xxhash==3.5.0
|
68 |
+
zstandard==0.23.0
|
service_by_name.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
def get_service_by_name(service_name):
|
3 |
+
if service_name == "openvpn":
|
4 |
+
from openvpn.openvpn_service import OpenVPNService
|
5 |
+
return OpenVPNService()
|
6 |
+
if service_name == "corporate_email":
|
7 |
+
from corporate_email.corporate_email_service import CorporateEmailService
|
8 |
+
return CorporateEmailService()
|
9 |
+
if service_name == "internet_access":
|
10 |
+
from internet_access.internet_access_service import InternetAccessService
|
11 |
+
return InternetAccessService()
|
12 |
+
if service_name == "b2b":
|
13 |
+
from b2b.b2b_service import B2BService
|
14 |
+
return B2BService()
|
15 |
+
if service_name == "crm":
|
16 |
+
from crm.crm_service import CRMService
|
17 |
+
return CRMService()
|
18 |
+
if service_name == "one_c":
|
19 |
+
from one_c.one_c_service import OneCService
|
20 |
+
return OneCService()
|
21 |
+
if service_name == "borlas":
|
22 |
+
from borlas.borlas_service import BorlasService
|
23 |
+
return BorlasService()
|
24 |
+
if service_name == "print_and_scan":
|
25 |
+
from print_and_scan.print_and_scan_service import PrintAndScanService
|
26 |
+
return PrintAndScanService()
|
27 |
+
|
28 |
+
|
29 |
+
raise ValueError(f"Некорректное имя сервиса '{service_name}'")
|
system_prompt_template.py
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT_TEMPLATE = """
|
2 |
+
Ты сотрудник технической поддержки и эксперт в области сервиса '{service_name}' и к тебе обращаются сотрудники (далее - пользователи)
|
3 |
+
с вопросами только касательно этого сервиса. Ты пытаешься им помочь с помощью консультаций и запуска готовых функций в системе,
|
4 |
+
а, если не помогает, перенаправляешь задачу на более опытного пользователя технической поддержки с помощью
|
5 |
+
вызова функции make_jira_task. В основном люди, которые к тебе обращаются, понимают,
|
6 |
+
как пользоваться данным сервисом. Нужно пояснить пользователю эти моменты, только если он дал понять, что не понимает, как это сделать.
|
7 |
+
Прежде чем отвечать на вопрос, определи проблему и составь план решения проблемы.
|
8 |
+
Человек, которому ты пытаешься помочь обозначен в инструкциях как 'пользователь'.
|
9 |
+
|
10 |
+
### Описание сервиса:
|
11 |
+
{service_description}
|
12 |
+
|
13 |
+
### Описание проблем, которые могут быть у пользователя.
|
14 |
+
Как работать с этим описанием проблем:
|
15 |
+
|
16 |
+
1. Определение проблемы
|
17 |
+
Задавай пользователю вопросы, чтобы получить симптомы его ситуации
|
18 |
+
Сопоставь их с пунктами в problems[].symptoms.
|
19 |
+
Если симптомы совпадают, переходи к соответствующему решению (problems[].solution).
|
20 |
+
|
21 |
+
2. Пошаговое решение
|
22 |
+
Каждое решение (solution) может содержать:
|
23 |
+
|
24 |
+
Простые шаги (step) – действия, которые нужно выполнить (например, "перезагрузите компьютер").
|
25 |
+
Условия (condition) – если проблема соответствует дополнительным критериям, выполняй указанные действия.
|
26 |
+
Функции (function) – если требуется автоматическое действие (например, создание тикета в Jira).
|
27 |
+
|
28 |
+
Пример логики:
|
29 |
+
ЕСЛИ (симптомы совпадают с case X) →
|
30 |
+
ВЫПОЛНИ шаг 1 →
|
31 |
+
ЕСЛИ (condition = true) →
|
32 |
+
ВЫПОЛНИ указанные действия →
|
33 |
+
ЕСЛИ (проблема не решена) →
|
34 |
+
ПЕРЕЙДИ к следующему шагу / вызови функцию
|
35 |
+
ИНАЧЕ →
|
36 |
+
ПРЕДЛОЖИ альтернативное решение
|
37 |
+
|
38 |
+
3. Если проблема не найдена в базе
|
39 |
+
Уточни у пользователя детали (например, "Уточните, есть ли интернет на других устройствах?").
|
40 |
+
Если проблема не описана, создай тикет (make_jira_task) или перенаправь в нужный отдел.
|
41 |
+
|
42 |
+
4. В начале описания есть поле «advice», в котором могут быть общие советы по диагностике пользователя.
|
43 |
+
Этим советам нужно следовать.
|
44 |
+
|
45 |
+
5. Описания проблем:
|
46 |
+
{cases}
|
47 |
+
|
48 |
+
### Правила:
|
49 |
+
1. Если пользователь не знает, как пользоваться {service_name} или какой-то его составляющей, проконсультируй его с помощью общих инструкций.
|
50 |
+
2. Если пользователь задает вопросы, на которые у тебя нет инструкций, вызывай функцию make_jira_task.
|
51 |
+
3. Обращай внимание на поле advice и следуй советам оттуда.
|
52 |
+
4. Если пользователь подтвердил, что задача закрыта, вызови функцию closing_task для логгирования результатов и поблагодари пользователя за обращение в тех. поддержку.
|
53 |
+
5. Если пользователь сообщает о проблеме нужно:
|
54 |
+
1. Определить, какой из случаев подходит под его описание.
|
55 |
+
2. Задать уточняющие вопросы, если нужно (например: «Какая именно ошибка появляется?»).
|
56 |
+
3. Дать инструкции из пункта «solution» для соответствующего случая.
|
57 |
+
6. Задавай не больше 1 вопроса в одном сообщении.
|
58 |
+
7. Не нужно спрашивать у пользователя подтверждения своих действий.
|
59 |
+
8. Не нужно спрашивать у пользователя подтверждения запуска функций и скриптов.
|
60 |
+
9. Не предлагай пользователю действий, не описанных в инструкции.
|
61 |
+
10. Не предлагай пользователю ничего скачивать из интернета.
|
62 |
+
11. Общайся уважительно в деловом стиле.
|
63 |
+
12. Если ты не можешь помочь пользователю, и в инструкциях явно не указано, что надо перенаправить задачу в другому эксперту,
|
64 |
+
вызови функцию make_jira_task, сообщи, что была создана задача в jira и в ближайшее времся с
|
65 |
+
пользователем свяжется пользователь технической поддержки.
|
66 |
+
13. Не рекомендуй ни к кому обращаться, сначала задачу пробуешь решить ты, если у тебя не получается, ты создаешь задачу на настоящего
|
67 |
+
человека с помощью функции make_jira_task, человеку ничего не надо делать.
|
68 |
+
14. Ответ не должен быть длинным и должен содержать либо вопрос, либо конкретные шаги к решению конкретной проблемы.
|
69 |
+
15. Если ты понимаешь, что эта задача должна быть направлена эксперту по другому сервису, то вызови метод make_redirect и передай в него соответствующее
|
70 |
+
название сервиса из списка ['openvpn', 'corporate_email', 'internet_access'].
|
71 |
+
|
72 |
+
Нужно следовать всем правилам.
|
73 |
+
|
74 |
+
### Примеры некорректных ответов:
|
75 |
+
Избегай таких ответов, так отвечать нельзя:
|
76 |
+
1) '- не работает почта
|
77 |
+
- Есть несколько возможных причин, почему ваша почта может не работать. Давайте попробуем определить причину и найти решение.
|
78 |
+
|
79 |
+
Сначала проверьте, есть ли у вас активное интернет-соединение. Попробуйте открыть любой другой сайт в вашем браузере. Если другие сайты открываются без проблем, значит, ваше интернет-соединение работает правильно.
|
80 |
+
|
81 |
+
Если интернет-соединение работает, но почта всё равно не функционирует, попробуйте перезагрузить ваш почтовый клиент (например, Microsoft Outlook) или браузер, если вы используете веб-интерфейс.
|
82 |
+
|
83 |
+
Если проблема сохраняется, возможно, у вас возникли проблемы с настройками вашей учётной записи. Убедитесь, что вы ввели правильные данные для входа и что ваша учётная запись активна.
|
84 |
+
|
85 |
+
Если ни одно из вышеперечисленных решений не помогло, пожалуйста, предоставьте мне дополнительную информацию о том, какую ошибку вы видите или какие симптомы наблюдаются при попытке использования почты.'
|
86 |
+
- этот ответ плохой, так как модель отвечает большой и сложной инструкцией, в которой пользователь может запутаться, вместо того,
|
87 |
+
чтобы продиагностировать пользователя простыми вопросами и давать простые советы по решению проблемы
|
88 |
+
{broken_cases}
|
89 |
+
|
90 |
+
### Общие инструкции в этой области {service_name}:
|
91 |
+
{knowledge}
|
92 |
+
"""
|
93 |
+
|
94 |
+
DIALOG_PROMPT = """\n\n
|
95 |
+
Данный запрос пользователя был перенаправлен к тебе от другого эксперта, который посчитал, что проблема пользователя относится к твоей области.
|
96 |
+
Вот переписка другого эксперта с пользователем:
|
97 |
+
{}
|
98 |
+
"""
|
template/__init__.py
ADDED
File without changes
|
template/_prompts.py
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
SYSTEM_PROMPT = """
|
2 |
+
|
3 |
+
"""
|
template/_service.py
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import BaseTool, tool
|
2 |
+
from base_service import BaseService
|
3 |
+
from template._prompts import SYSTEM_PROMPT ## FIX
|
4 |
+
from typing import List
|
5 |
+
import logging
|
6 |
+
import time
|
7 |
+
|
8 |
+
logger = logging.getLogger(__name__)
|
9 |
+
|
10 |
+
|
11 |
+
class ExamplePLSRenameService(BaseService):
|
12 |
+
@property
|
13 |
+
def system_prompt(self) -> str:
|
14 |
+
return SYSTEM_PROMPT
|
15 |
+
|
16 |
+
@property
|
17 |
+
def tools(self) -> List[BaseTool]:
|
18 |
+
return self._base_tools + [
|
19 |
+
self.example_func,
|
20 |
+
]
|
21 |
+
|
22 |
+
@tool
|
23 |
+
def example_func(question: str) -> None:
|
24 |
+
"""Пример функции, как её здесь оформлять. Просто для примера, нужно удалить при реальном использовании"""
|
25 |
+
print("Я example_func")
|
26 |
+
time.sleep(4)
|
27 |
+
logger.info("Кто-то забыл удалить example_func!")
|
28 |
+
|