Интеллектуальный бот службы поддержки клиентов LangChain
В этой статье объединяются знания, полученные ранее, чтобы создать полноценного интеллектуального бота службы поддержки клиентов. Он умеет искать по базе знаний, обрабатывать заказы и при необходимости переводить на живого оператора.
Анализ требований
| Характеристика | Реализация |
|---|---|
| Вопросы и ответы по базе знаний | RAG-поиск + ответ модели |
| Запрос по заказу | Функция инструмента @tool |
| Память диалога | Чекпоинтер SqliteSaver |
| Фильтрация чувствительного контента | Промежуточное ПО @before_model |
| Перевод на живого оператора | HITL interrupt() / Command(resume=...) |
Настройка окружения
Прежде чем писать сам код, сначала подготовьте среду выполнения. Выполните шаги ниже, и она будет настроена за несколько минут.
Шаг 1: Проверка версии Python
Рекомендуется Python 3.10 или выше. Откройте терминал и введите:
python --version
Если ваша версия ниже 3.10, рекомендуется сначала обновить Python, прежде чем переходить к следующим шагам.
Шаг 2: Создание и активация виртуального окружения (рекомендуется)
Чтобы не загрязнять системное окружение Python, рекомендуется создать отдельное виртуальное окружение для этого проекта:
# 创建虚拟环境 python -m venv venv # 激活虚拟环境(Windows) venv\Scripts\activate # 激活虚拟环境(macOS / Linux) source venv/bin/activate
После успешной активации приглашение терминала отобразит(venv)text.
Шаг 3: Установка зависимостей
В этом проекте используется всего 6 сторонних библиотек, каждая отвечает за разные функции. Рекомендуется устанавливать их по одной в следующем порядке, чтобы было проще определить, какая библиотека установлена неправильно, если возникнет проблема:
| Команда установки | Назначение |
|---|---|
pip install langchain | Основная библиотека LangChain, предоставляющая базовые возможности, такие как create_agent и @tool |
pip install langchain-deepseek | Позволяет init_chat_model распознавать префикс "deepseek:" и вызывать модель DeepSeek |
pip install langchain-openai | Предоставляет OpenAIEmbeddings для преобразования текста базы знаний в векторы |
pip install langchain-chroma chromadb | Локальная векторная база данных для хранения и извлечения векторов базы знаний |
pip install langgraph-checkpoint-sqlite | SqliteSaver сохраняет память диалога в файл SQLite |
pip install python-dotenv | Считывает переменные среды, такие как ключи API, из файла .env |
Вы также можете установить их все одной командой:
pip install langchain langchain-deepseek langchain-openai langchain-chroma chromadb langgraph-checkpoint-sqlite python-dotenv
Шаг 4: Настройка ключей API
Создайте новый файл с именем.envв корневом каталоге проекта (обратите внимание: имя файла начинается с точки и не имеет расширения), и заполните следующим содержимым:
# DeepSeek 官网申请:https://platform.deepseek.com DEEPSEEK_API_KEY=sk-你的deepseek密钥 # OpenAI 官网申请:https://platform.openai.com # 这里只用来调用 embedding 接口,不涉及 Chat 模型 OPENAI_API_KEY=sk-你的openai密钥
Если у вас нет ключа OpenAI, вместо него можно использовать ключ Alibaba Bailian..envКод для файла .env выглядит следующим образом:
# DeepSeek 官网申请:https://platform.deepseek.com DEEPSEEK_API_KEY=sk-你的deepseek密钥 # 阿里云百炼控制台申请:https://bailian.console.aliyun.com # 这里用来调用通义千问的 Embedding 服务,给知识库文本做向量化 DASHSCOPE_API_KEY=sk-你的百炼密钥
Файл .env хранит приватные ключи. Обязательно не добавляйте его в Git-репозиторий и не делитесь им с другими. Вы можете создать новый
.gitignoreфайл в проекте и добавить строку.envчтобы избежать случайных коммитов.
Шаг 5: Проверка установки
Создайте новыйcheck_install.pyфайл, затем запустите приведённый ниже скрипт, чтобы проверить, правильно ли настроены зависимости и ключи:
Пример
import os
from dotenv import load_dotenv
load_dotenv()
# Проверьте, что пакеты зависимостей можно правильно импортировать
import langchain
import langchain_deepseek
import langchain_openai
import langchain_chroma
import chromadb
import langgraph
print(f"версия langchain: {langchain.__version__}")
# Проверьте, настроены ли ключи
assert os.getenv("DEEPSEEK_API_KEY"), "DEEPSEEK_API_KEY не обнаружен, проверьте файл .env"
# assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY не обнаружен, проверьте файл .env"
assert os.getenv("DASHSCOPE_API_KEY"), "DASHSCOPE_API_KEY не обнаружен, проверьте файл .env"
print("Настройка окружения выполнена успешно~ Теперь можно приступить к написанию бота службы поддержки!")
Запустите:
python check_install.py
Если ключи настроены правильно, вывод будет следующим:
langchain 版本: 1.3.0 环境配置成功~可以开始写客服机器人了!
Если возникает ошибка, обычно это связано с тем, что пакет не установлен или ключи в файле .env неверны. Проверьте соответствующий шаг в соответствии с сообщением об ошибке.
Полный код
После настройки окружения (установка зависимостей и настройка .env описаны в предыдущем разделе "Настройка окружения"), можно написать полный код бота службы поддержки:
Пример
# Установка зависимостей и настройка .env описаны в предыдущем разделе "Настройка окружения"
from dotenv import load_dotenv
load_dotenv()
import os
import sqlite3
from typing import Annotated
from langchain.tools import tool
from langchain.agents import create_agent
from langchain.agents.middleware import before_model, after_model
from langchain.chat_models import init_chat_model
from langchain.messages import HumanMessage, AIMessage
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.types import interrupt, Command
# ========== 1. Подготовьте базу знаний ==========
knowledge_base = [
"EXAMPLE — ведущая бесплатная платформа для изучения программирования в Китае, основанная в 2013 году.",
"Платформа предлагает 300+ руководств, охватывающих Python, Java, HTML, CSS, JavaScript и другие.",
"Базовый учебник по Python3 состоит из 30 глав, общее число учащихся превысило 5 миллионов. Курс полностью бесплатный.",
"VIP-членство стоит ¥99/месяц или ¥799/год, включая видеокурсы и поддержку в формате вопросов и ответов один на один.",
"Политика возврата: полный возврат доступен в течение 7 дней после покупки и в пределах 3 уроков.",
"Платформа поддерживает онлайн-среду программирования, позволяя писать и запускать код без установки какого-либо программного обеспечения.",
"Часы работы службы поддержки: с понедельника по пятницу 9:00-18:00, в выходные 10:00-16:00.",
]
# Использование сервиса Embedding Tongyi Qianwen от Alibaba Cloud Bailian (DashScope)
# API Embedding от Bailian совместим со спецификацией OpenAI API, поэтому мы напрямую используем langchain-openai
# OpenAIEmbeddings и укажите base_url на совместимый эндпоинт Bailian,
# нет необходимости устанавливать langchain-community / dashscope (этот пакет больше не поддерживается).
# text-embedding-v4 в настоящее время является рекомендуемой универсальной векторной моделью, по умолчанию выдающей 1024-мерные векторы.
#
# Требуются два ключевых параметра:
# - check_embedding_ctx_length=False: OpenAIEmbeddings по умолчанию использует tiktoken
# чтобы предварительно закодировать текст в массив token id перед отправкой (официальный OpenAI API принимает этот формат),
# но совместимый API Bailian принимает только необработанные строки. Если не отключить эту опцию, будет выброшена
# ошибка "contents is neither str nor list of str".
# - chunk_size=10: API Embedding от Bailian принимает не более 10 текстовых элементов на запрос,
# При этом OpenAIEmbeddings по умолчанию объединяет в пакеты по 1000 элементов за раз. Если база знаний немного велика, будет превышен лимит и возникнет ошибка.
embeddings = OpenAIEmbeddings(
model="text-embedding-v4",
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
check_embedding_ctx_length=False,
chunk_size=10,
)
chunks = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=30
).create_documents(knowledge_base)
vector_store = Chroma.from_documents(chunks, embeddings)
retriever = vector_store.as_retriever(search_kwargs={"k": 3})
# ========== 2. Определение инструментов ==========
@tool
def search_kb(query: str) -> str:
"""Поиск в EXAMPLE базе знаний официальной информации о платформе, курсах, политиках и т. д.
Аргументы:
query: поисковый вопрос или ключевое слово
"""
docs = retriever.invoke(query)
if not docs:
return "Соответствующая информация не найдена. Рекомендуется передать оператору-человеку."
return "\n".join(f"- {doc.page_content}" for doc in docs)
# Мок-база данных заказов
orders_db = {
"ORD-2024-001": {"user": "Сяомин", "item": "Годовое VIP-членство",
"amount": 799, "status": "Завершён", "date": "2024-01-15"},
"ORD-2024-002": {"user": "Сяомин", "item": "Практический курс Python",
"amount": 199, "status": "В пути", "date": "2024-03-20"},
}
@tool
def query_order(order_id: str) -> str:
"""Запросить статус и детали заказа по номеру заказа.
Аргументы:
order_id: номер заказа, например, ORD-2024-001
"""
order = orders_db.get(order_id.upper())
if not order:
return f"Заказ {order_id} не найден. Пожалуйста, проверьте номер заказа."
return (f"Заказ {order_id}: {order['item']} | "
f"Сумма ¥{order['amount']} | "
f"Статус {order['status']} | "
f"Дата {order['date']}")
@tool
def transfer_to_human(reason: str) -> str:
"""Перевести пользователя к оператору-человеку.
Аргументы:
reason: причина перевода
"""
approval = interrupt({
"action": "transfer_to_human",
"reason": reason,
"message": f"Пользователь запрашивает перевод к живому оператору. Причина: {reason}. Вы хотите выполнить перевод?"
})
if approval.get("confirmed"):
return (f"Вы были переведены к живому оператору. Ожидаемое время ожидания: {approval.get('wait_time', 3)} минут."
f"Номер заявки: TK-{approval.get('ticket_id', 'N/A')}")
return "Перевод отменён, я продолжу вас обслуживать."
# ========== 3. Определение Middleware ==========
@before_model
def content_guard(state, runtime):
"""Фильтровать недопустимый контент во вводе пользователя"""
last_msg = state["messages"][-1] if state.get("messages") else None
if not last_msg:
return None
content = str(getattr(last_msg, 'content', ''))
blocked = ["Huang X", "X Bo", "незаконный"]
for word in blocked:
if word in content:
return {
"jump_to": "end",
"messages": [HumanMessage(content="Извините, я не могу обработать этот запрос.")]
}
return None
@after_model
def auto_signature(state, runtime):
"""Автоматически добавлять подпись службы поддержки"""
msgs = state.get("messages", [])
if not msgs:
return None
last = msgs[-1]
if last.type == "ai" and last.content and not (
hasattr(last, 'tool_calls') and last.tool_calls
):
# Ключ: повторно используйте last.id, чтобы редьюсер add_messages заменял сообщение на месте,
# а не добавлять его как новое сообщение в историю (иначе история будет продолжать расти,
# при каждом раунде добавляя одну "неподписанную версию" и одну "подписанную версию")
return {"messages": [AIMessage(
id=last.id,
content=last.content
+ "\n\n---\n"Центр обслуживания клиентов EXAMPLE | Часы работы: 9:00-18:00"
)]}
return None
# ========== 4. Создать агента ==========
# SqliteSaver.from_conn_string() возвращает менеджер контекста, подходящий только для "закрытия после использования"
# одноразовые скрипты. Боту службы поддержки необходимо сохранять одно и то же соединение с базой данных между несколькими вызовами chat(),
# поэтому здесь мы сами устанавливаем соединение и передаём его в конструктор SqliteSaver.
# check_same_thread=False нужен потому, что веб-фреймворки используют одно и то же соединение в разных потоках.
conn = sqlite3.connect("customer_service.db", check_same_thread=False)
checkpointer = SqliteSaver(conn)
model = init_chat_model("deepseek:deepseek-v4-flash", temperature=0)
agent = create_agent(
model=model,
tools=[search_kb, query_order, transfer_to_human],
middleware=[content_guard, auto_signature],
checkpointer=checkpointer,
system_prompt=""Вы — "Сяо Цай", интеллектуальный агент службы поддержки клиентов EXAMPLE.
## Ваш ИИ
Приветливо встречайте каждого пользователя и обращайтесь к ним на «вы» (вежливая форма «вы»)
2. По вопросам об информации о платформе, содержании курсов, политиках и т. д. используйте search_kb для их поиска
3. По вопросам заказов используйте инструмент query_order
4. При возникновении проблем, которые вы не можете решить, используйте transfer_to_human для перевода на оператора-человека
## Кодекс поведения
- Отвечайте кратко, по 2-3 предложения каждый раз
- Если не знаете, обратитесь к базе знаний; если не найдёте, честно сообщите пользователю
- Поддерживайте дружелюбный и тёплый тон""",
)
# ========== 5. Интерфейс диалога ==========
def chat(thread_id: str, message: str) -> str:
"""Обрабатывает сообщения пользователей и возвращает ответы"""
config = {"configurable": {"thread_id": thread_id}}
# Запуск агента
result = agent.invoke(
{"messages": [HumanMessage(content=message)]},
config=config,
)
# Проверить, нужна ли передача (HITL)
state = agent.get_state(config)
if state.tasks and state.tasks[0].interrupts:
interrupt_info = state.tasks[0].interrupts[0].value
return f"[Требуется одобрение] {interrupt_info.get('message', '')}"
return result["messages"][-1].content
def resume_transfer(thread_id: str, confirmed: bool,
wait_time: int = 3, ticket_id: str = "0001") -> str:
"""После того как человек-агент одобрит во бэкенде, возобновите процесс передачи, прерванный interrupt().
Это соответствует данным одобрения, ожидающим в инструменте transfer_to_human.
"""
config = {"configurable": {"thread_id": thread_id}}
result = agent.invoke(
Команда(resume={
"confirmed": confirmed,
"wait_time": wait_time,
"ticket_id": ticket_id,
}),
config=config,
)
return result["messages"][-1].content
# ========== 6. Тесты ==========
if __name__ == "__main__":
user_id = "user_xiaoming"
print("=== Тест 1: Запрос к базе знаний ===")
print(chat(user_id, "Сколько глав в учебнике по Python3?"))
print()
print("=== Тест 2: Запрос по заказу ===")
print(chat(user_id, "Каков статус моего заказа ORD-2024-001?"))
print()
print("=== Тест 3: VIP-консультация ===")
print(chat(user_id, "Сколько стоит VIP-членство?"))
print()
print("=== Тест 4: Проверка памяти ===")
print(chat(user_id, "Какой вопрос я только что задал?"))
print()
print("=== Тест 5: Передача человеку (HITL) ===")
print(chat(user_id, "Я хочу подать жалобу, пожалуйста, переведите меня на живого оператора.")) # Вызвать прерывание, дождаться одобрения
print(resume_transfer(user_id, confirmed=True,
wait_time=5, ticket_id="8823")) # Возобновить после подтверждения бэкенда
conn.close()
Результаты запуска:
=== 测试 1:知识库查询 === 您好!Example的 **Python3 基础教程共 30 章**,完全免费,累计学习人次已超 500 万哦!如需了解其他教程,也可以随时问我~ --- Example 客服中心 | 工作时间 9:00-18:00 === 测试 2:订单查询 === 您好!您查询的订单 **ORD-2024-001** 状态为 **已完成**。订单内容是 **VIP 年费会员**,金额 **¥799**,下单日期为 **2024-01-15**。请问还有什么可以帮您的吗? --- Example 客服中心 | 工作时间 9:00-18:00 === 测试 3:VIP 咨询 === 您好!VIP 会员有 **¥99/月** 和 **¥799/年** 两种套餐,包含视频课程和一对一答疑服务哦~请问您需要办理哪种呢? --- Example 客服中心 | 工作时间 9:00-18:00 --- Example 客服中心 | 工作时间 9:00-18:00 === 测试 4:测试记忆 === 您好!您刚才问了以下三个问题: 1. **Python3 教程有多少章?** —— 共 30 章,完全免费 2. **我的订单 ORD-2024-001 状态是什么?** —— 状态为"已完成" 3. **VIP 会员多少钱?** —— ¥99/月 или ¥799/年 还有什么需要我帮忙的吗? --- Example 客服中心 | 工作时间 9:00-18:00 --- Example 客服中心 | 工作时间 9:00-18:00 === 测试 5:人工转接(HITL) === [需要审批] 用户请求转接人工客服,原因:用户要求投诉,需要转接人工客服处理。是否转接? 您好!已经为您转接人工客服,预计等待约 5 分钟。您的工单号是 **TK-8823**,请稍候,客服会尽快为您处理~ --- Example 客服中心 | 工作时间 9:00-18:00 --- Example 客服中心 | 工作时间 9:00-18:00
Сводка проекта
Этот бот службы поддержки интегрирует следующие возможности LangChain:
| Характеристика | Использование в проекте |
|---|---|
| RAG-поиск | инструмент search_kb + векторное хранилище Chroma |
| Вызов инструментов | query_order、transfer_to_human |
| Checkpointer | SqliteSaver сохраняет диалоги, обеспечивая многоходовую память |
| Промежуточное ПО | before_model фильтрация содержимого + after_model добавление подписи (повторно использовать id сообщения для замены на месте) |
| HITL | interrupt() приостанавливает выполнение + Command(resume=...) возобновляет после одобрения, обеспечивая полный цикл передачи человеку |