155 lines
4.7 KiB
Python
155 lines
4.7 KiB
Python
"""
|
|
AI 客服核心逻辑 - 基于 LangGraph
|
|
"""
|
|
from typing import TypedDict, Annotated, Sequence
|
|
import operator
|
|
|
|
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
|
|
from langchain_openai import ChatOpenAI
|
|
from langgraph.graph import StateGraph, END
|
|
from langgraph.checkpoint.memory import MemorySaver
|
|
|
|
from config import LLM_API_KEY, LLM_BASE_URL, LLM_MODEL, SYSTEM_PROMPT
|
|
from knowledge import get_knowledge_base
|
|
|
|
|
|
# 定义状态类型
|
|
class ChatState(TypedDict):
|
|
"""对话状态"""
|
|
messages: Annotated[Sequence[BaseMessage], operator.add] # 消息历史
|
|
context: str # 检索到的知识上下文
|
|
session_id: str # 会话ID
|
|
|
|
|
|
class AIChatbot:
|
|
"""AI 客服机器人"""
|
|
|
|
def __init__(self):
|
|
# 初始化 LLM
|
|
self.llm = ChatOpenAI(
|
|
model=LLM_MODEL,
|
|
api_key=LLM_API_KEY,
|
|
base_url=LLM_BASE_URL,
|
|
temperature=0.7,
|
|
max_tokens=1000
|
|
)
|
|
|
|
# 获取知识库
|
|
self.knowledge_base = get_knowledge_base()
|
|
|
|
# 构建工作流
|
|
self.workflow = self._build_workflow()
|
|
|
|
def _retrieve_knowledge(self, state: ChatState) -> ChatState:
|
|
"""检索相关知识"""
|
|
# 获取最后一条用户消息
|
|
last_message = state["messages"][-1]
|
|
if not isinstance(last_message, HumanMessage):
|
|
return state
|
|
|
|
query = last_message.content
|
|
|
|
# 从知识库检索
|
|
docs = self.knowledge_base.search(query)
|
|
|
|
# 构建上下文
|
|
if docs:
|
|
context = "\n\n".join([f"[知识库内容 {i+1}]\n{doc.page_content}"
|
|
for i, doc in enumerate(docs)])
|
|
else:
|
|
context = "【知识库中没有找到相关信息】"
|
|
|
|
return {"context": context, "messages": state["messages"], "session_id": state["session_id"]}
|
|
|
|
def _generate_response(self, state: ChatState) -> ChatState:
|
|
"""生成回答"""
|
|
# 构建系统提示
|
|
system_prompt = f"""{SYSTEM_PROMPT}
|
|
|
|
【知识库信息】
|
|
{state['context']}
|
|
|
|
请基于以上知识库信息回答用户问题。如果知识库中没有相关信息,请诚实地告诉用户你无法回答。
|
|
"""
|
|
|
|
# 构建消息列表
|
|
messages = [SystemMessage(content=system_prompt)] + list(state["messages"])
|
|
|
|
# 调用 LLM
|
|
response = self.llm.invoke(messages)
|
|
|
|
# 返回新的消息(会自动添加到历史)
|
|
return {"messages": [response], "context": state["context"], "session_id": state["session_id"]}
|
|
|
|
def _build_workflow(self) -> StateGraph:
|
|
"""构建 LangGraph 工作流"""
|
|
# 创建工作流
|
|
workflow = StateGraph(ChatState)
|
|
|
|
# 添加节点
|
|
workflow.add_node("retrieve", self._retrieve_knowledge)
|
|
workflow.add_node("generate", self._generate_response)
|
|
|
|
# 添加边
|
|
workflow.set_entry_point("retrieve")
|
|
workflow.add_edge("retrieve", "generate")
|
|
workflow.add_edge("generate", END)
|
|
|
|
# 编译(添加内存检查点)
|
|
memory = MemorySaver()
|
|
return workflow.compile(checkpointer=memory)
|
|
|
|
async def chat(self, message: str, session_id: str) -> str:
|
|
"""
|
|
处理用户消息
|
|
|
|
Args:
|
|
message: 用户消息
|
|
session_id: 会话ID(用于区分不同用户)
|
|
|
|
Returns:
|
|
AI 回复
|
|
"""
|
|
# 构建配置
|
|
config = {"configurable": {"thread_id": session_id}}
|
|
|
|
# 运行工作流
|
|
result = await self.workflow.ainvoke(
|
|
{
|
|
"messages": [HumanMessage(content=message)],
|
|
"context": "",
|
|
"session_id": session_id
|
|
},
|
|
config
|
|
)
|
|
|
|
# 返回最后一条 AI 消息
|
|
last_message = result["messages"][-1]
|
|
if isinstance(last_message, AIMessage):
|
|
return last_message.content
|
|
return str(last_message)
|
|
|
|
def get_history(self, session_id: str) -> list:
|
|
"""获取对话历史"""
|
|
config = {"configurable": {"thread_id": session_id}}
|
|
# 从 checkpointer 获取状态
|
|
state = self.workflow.get_state(config)
|
|
if state and "messages" in state.values:
|
|
return [
|
|
{"role": "user" if isinstance(m, HumanMessage) else "assistant",
|
|
"content": m.content}
|
|
for m in state.values["messages"]
|
|
]
|
|
return []
|
|
|
|
|
|
# 全局客服实例
|
|
_chatbot = None
|
|
|
|
def get_chatbot() -> AIChatbot:
|
|
"""获取客服单例"""
|
|
global _chatbot
|
|
if _chatbot is None:
|
|
_chatbot = AIChatbot()
|
|
return _chatbot
|