基于Python構(gòu)建一個(gè)智能對(duì)話系統(tǒng)
在當(dāng)今信息爆炸的時(shí)代,如何有效管理和組織對(duì)話記錄成為了一個(gè)重要挑戰(zhàn)。本文將介紹如何使用Python構(gòu)建一個(gè)智能對(duì)話系統(tǒng),該系統(tǒng)能夠自動(dòng)識(shí)別對(duì)話話題、生成摘要,并提供智能對(duì)話管理功能。
系統(tǒng)概述
這個(gè)智能對(duì)話系統(tǒng)的核心功能包括:
- 對(duì)話記錄管理:使用SQLite數(shù)據(jù)庫(kù)持久化存儲(chǔ)聊天記錄
- 話題自動(dòng)識(shí)別:將相關(guān)對(duì)話內(nèi)容聚類成話題
- 智能摘要生成:對(duì)每個(gè)話題生成簡(jiǎn)潔的摘要
- 多智能體協(xié)作:使用多個(gè)AI智能體分工處理不同任務(wù)
下面我們來詳細(xì)解析系統(tǒng)的各個(gè)組成部分。
環(huán)境準(zhǔn)備與依賴安裝
首先,確保你已安裝Python 3.7+,然后安裝必要的依賴包:
pip install langchain langchain-community langchain-openai
如果你的OpenAI模型是通過LM Studio本地部署的,還需要配置相應(yīng)的API端點(diǎn)。
項(xiàng)目結(jié)構(gòu)設(shè)計(jì)
合理的項(xiàng)目結(jié)構(gòu)是代碼可維護(hù)性的基礎(chǔ),我們的項(xiàng)目結(jié)構(gòu)如下:
chat_system/
├── chat_manager.py # 主程序文件
├── requirements.txt # 依賴列表
└── chat_topics.db # SQLite數(shù)據(jù)庫(kù)(自動(dòng)生成)
核心代碼解析
1. 數(shù)據(jù)模型設(shè)計(jì)
我們首先定義兩個(gè)核心數(shù)據(jù)模型:話題(Topic)和聊天消息(ChatMessage)。
class Topic:
def __init__(self, topic_id: str, topic: str, summary: str, message_ids: List[str]):
self.topic_id = topic_id
self.topic = topic
self.summary = summary
self.message_ids = message_ids
class ChatMessage:
def __init__(self, message_id: str, role: str, content: str, timestamp: datetime = None):
self.message_id = message_id
self.role = role
self.content = content
self.timestamp = timestamp or datetime.now()
2. 數(shù)據(jù)庫(kù)管理
我們使用SQLite數(shù)據(jù)庫(kù)進(jìn)行數(shù)據(jù)持久化存儲(chǔ),通過DatabaseManager類統(tǒng)一管理數(shù)據(jù)庫(kù)操作。
class DatabaseManager:
def __init__(self, db_path: str = "chat_topics.db"):
self.db_path = db_path
self.init_db()
def init_db(self):
"""初始化數(shù)據(jù)庫(kù)表"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 創(chuàng)建topics表
cursor.execute("""
CREATE TABLE IF NOT EXISTS topics (
topic_id TEXT PRIMARY KEY,
topic TEXT NOT NULL,
summary TEXT,
message_ids TEXT
)
""")
# 創(chuàng)建chat_messages表
cursor.execute("""
CREATE TABLE IF NOT EXISTS chat_messages (
message_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
conn.close()
3. 核心功能工具
系統(tǒng)提供了三個(gè)核心工具函數(shù),分別負(fù)責(zé)話題選擇、歷史記錄獲取和話題壓縮。
def select_topic_tool(topic_ids: List[str], summary_only: Union[List[bool], bool] = False) -> List[Dict[str, Any]]:
"""根據(jù)topic_ids選擇話題的工具"""
# 實(shí)現(xiàn)細(xì)節(jié)...
def get_history(session_id: str) -> List[Dict[str, Any]]:
"""獲取當(dāng)前會(huì)話的聊天記錄"""
# 實(shí)現(xiàn)細(xì)節(jié)...
def compress_history_to_topic(session_id: str, new_messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""將聊天記錄壓縮成話題"""
# 實(shí)現(xiàn)細(xì)節(jié)...
4. 多智能體系統(tǒng)
系統(tǒng)采用多智能體架構(gòu),每個(gè)智能體負(fù)責(zé)不同的任務(wù):
- 歷史信息檢索智能體:負(fù)責(zé)檢索相關(guān)的歷史話題
- 問答智能體:基于上下文信息回答用戶問題
- 話題總結(jié)智能體:將對(duì)話記錄壓縮成結(jié)構(gòu)化話題
# 創(chuàng)建智能體執(zhí)行器
history_retrieval_agent = AgentExecutor(
agent=history_retrieval_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
# 類似創(chuàng)建其他智能體...
5. 主協(xié)調(diào)函數(shù)
super_agent函數(shù)作為系統(tǒng)的主入口,協(xié)調(diào)各個(gè)智能體協(xié)作處理用戶輸入。
def super_agent(user_input: str, session_id: str):
"""超級(jí)智能體主函數(shù)"""
if not session_id:
session_id = str(uuid.uuid4())
# 保存用戶輸入
user_message = ChatMessage(str(uuid.uuid4()), "user", user_input)
db_manager.save_chat_message(session_id, user_message)
# 1. 歷史信息檢索智能體
print("=== 歷史信息檢索智能體 ===")
# ... 檢索相關(guān)話題
# 2. 使用檢測(cè)信息回答用戶問題智能體
print("=== 問答智能體 ===")
# ... 生成回答
# 3. 處理歷史信息 話題總結(jié)智能體
print("=== 話題總結(jié)智能體 ===")
# ... 壓縮對(duì)話為話題
return {
"session_id": session_id,
"qa_response": qa_response,
"new_topics": new_topic
}
系統(tǒng)特色與優(yōu)勢(shì)
1. 模塊化設(shè)計(jì)
系統(tǒng)采用高度模塊化的設(shè)計(jì),各個(gè)組件職責(zé)分明,便于維護(hù)和擴(kuò)展。
2. 智能話題識(shí)別
系統(tǒng)能夠自動(dòng)識(shí)別對(duì)話中的主題,并將相關(guān)對(duì)話內(nèi)容聚類,大大提高了對(duì)話記錄的可管理性。
3. 靈活的數(shù)據(jù)持久化
使用SQLite數(shù)據(jù)庫(kù),既保證了數(shù)據(jù)的持久化,又避免了復(fù)雜數(shù)據(jù)庫(kù)的部署成本。
4. 多智能體協(xié)作
通過多個(gè)專門化的智能體分工合作,提高了系統(tǒng)的整體性能和準(zhǔn)確性。
實(shí)際應(yīng)用示例
下面是一個(gè)簡(jiǎn)單的使用示例:
if __name__ == "__main__":
result = super_agent("你好,能告訴我天氣怎么樣嗎?", "session_1")
print(f"最終結(jié)果: {result}")
系統(tǒng)會(huì)自動(dòng)處理用戶輸入,檢索相關(guān)歷史話題,生成回答,并將當(dāng)前對(duì)話壓縮成新話題。
擴(kuò)展方向
這個(gè)基礎(chǔ)系統(tǒng)有很多可能的擴(kuò)展方向:
- 用戶認(rèn)證系統(tǒng):添加用戶登錄和權(quán)限管理
- 話題可視化:提供圖形化界面展示話題關(guān)系
- 高級(jí)摘要算法:使用更先進(jìn)的NLP技術(shù)生成質(zhì)量更高的摘要
- 多語(yǔ)言支持:擴(kuò)展系統(tǒng)以支持多種語(yǔ)言
- 實(shí)時(shí)協(xié)作:支持多用戶同時(shí)使用和協(xié)作
完整代碼
import os
import uuid
import sqlite3
import json
from datetime import datetime
from typing import List, Dict, Any, Union
from langchain.agents import AgentExecutor, create_react_agent
from langchain.prompts import PromptTemplate
from langchain.tools import Tool
# 為避免棄用警告,使用條件導(dǎo)入
try:
from langchain_openai import ChatOpenAI
except ImportError:
from langchain_community.chat_models import ChatOpenAI
# 配置連接到LM Studio的LLM
llm = ChatOpenAI(
model="qwen/qwen3-1.7b",
openai_api_key="lm-studio",
openai_api_base="http://127.0.0.1:1234/v1",
temperature=0.7
)
class Topic:
def __init__(self, topic_id: str, topic: str, summary: str, message_ids: List[str]):
self.topic_id = topic_id
self.topic = topic
self.summary = summary
self.message_ids = message_ids
class ChatMessage:
def __init__(self, message_id: str, role: str, content: str, timestamp: datetime = None):
self.message_id = message_id
self.role = role
self.content = content
self.timestamp = timestamp or datetime.now()
class DatabaseManager:
def __init__(self, db_path: str = "chat_topics.db"):
self.db_path = db_path
self.init_db()
def init_db(self):
"""初始化數(shù)據(jù)庫(kù)表"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 創(chuàng)建topics表
cursor.execute("""
CREATE TABLE IF NOT EXISTS topics (
topic_id TEXT PRIMARY KEY,
topic TEXT NOT NULL,
summary TEXT,
message_ids TEXT
)
""")
# 創(chuàng)建chat_messages表
cursor.execute("""
CREATE TABLE IF NOT EXISTS chat_messages (
message_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
conn.close()
def save_topic(self, topic: Topic):
"""保存話題"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute(
"INSERT OR REPLACE INTO topics (topic_id, topic, summary, message_ids) VALUES (?, ?, ?, ?)",
(topic.topic_id, topic.topic, topic.summary, json.dumps(topic.message_ids))
)
conn.commit()
conn.close()
def get_all_topics(self) -> List[Topic]:
"""獲取所有話題"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT topic_id, topic, summary, message_ids FROM topics")
rows = cursor.fetchall()
conn.close()
topics = []
for row in rows:
topic_id, topic_str, summary, message_ids_str = row
message_ids = json.loads(message_ids_str) if message_ids_str else []
topics.append(Topic(topic_id, topic_str, summary, message_ids))
return topics
def save_chat_message(self, session_id: str, message: ChatMessage):
"""保存聊天消息"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 使用INSERT OR IGNORE避免重復(fù)插入
cursor.execute(
"INSERT OR IGNORE INTO chat_messages (message_id, session_id, role, content) VALUES (?, ?, ?, ?)",
(message.message_id, session_id, message.role, message.content)
)
conn.commit()
conn.close()
def get_session_messages(self, session_id: str) -> List[ChatMessage]:
"""獲取會(huì)話消息"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute(
"SELECT message_id, role, content, timestamp FROM chat_messages WHERE session_id = ? ORDER BY timestamp",
(session_id,)
)
rows = cursor.fetchall()
conn.close()
messages = []
for row in rows:
message_id, role, content, timestamp = row
# 處理時(shí)間戳格式
if isinstance(timestamp, str):
try:
timestamp = datetime.fromisoformat(timestamp)
except ValueError:
timestamp = datetime.now()
messages.append(ChatMessage(message_id, role, content, timestamp))
return messages
# 創(chuàng)建數(shù)據(jù)庫(kù)管理器實(shí)例
db_manager = DatabaseManager()
def select_topic_tool(topic_ids: List[str], summary_only: Union[List[bool], bool] = False) -> List[Dict[str, Any]]:
"""根據(jù)topic_ids選擇話題的工具,支持為每個(gè)topic_id指定summary_only參數(shù)"""
topics = db_manager.get_all_topics()
# 如果topic_ids為空,處理所有話題
if not topic_ids:
selected_topics = topics
# 如果summary_only是布爾值,則應(yīng)用于所有話題
if isinstance(summary_only, bool):
summary_flags = [summary_only] * len(selected_topics)
# 如果summary_only是列表,則按順序應(yīng)用
elif isinstance(summary_only, list):
summary_flags = summary_only
else:
summary_flags = [False] * len(selected_topics)
else:
# 根據(jù)指定的topic_ids篩選話題
selected_topics = [topic for topic in topics if topic.topic_id in topic_ids]
# 如果summary_only是布爾值,則應(yīng)用于所有選定話題
if isinstance(summary_only, bool):
summary_flags = [summary_only] * len(selected_topics)
# 如果summary_only是列表,則按順序應(yīng)用(如果長(zhǎng)度不夠則用False填充)
elif isinstance(summary_only, list):
summary_flags = summary_only[:len(selected_topics)]
# 如果summary_only列表長(zhǎng)度不足,用False填充
summary_flags.extend([False] * (len(selected_topics) - len(summary_flags)))
else:
summary_flags = [False] * len(selected_topics)
result = []
for i, topic in enumerate(selected_topics):
# 獲取對(duì)應(yīng)的summary_only標(biāo)志
show_summary_only = summary_flags[i] if i < len(summary_flags) else False
if show_summary_only:
result.append({
"topic_id": topic.topic_id,
"topic": topic.topic,
"summary": topic.summary
})
else:
result.append({
"topic_id": topic.topic_id,
"topic": topic.topic,
"summary": topic.summary,
"message_ids": topic.message_ids
})
return result
def get_history(session_id: str) -> List[Dict[str, Any]]:
"""獲取當(dāng)前會(huì)話的聊天記錄"""
messages = db_manager.get_session_messages(session_id)
return [{"role": msg.role, "content": msg.content, "message_id": msg.message_id} for msg in messages]
def compress_history_to_topic(session_id: str, new_messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""將新的聊天記錄壓縮成話題"""
# 這里應(yīng)該調(diào)用LLM來生成話題摘要,為了簡(jiǎn)化,我們使用簡(jiǎn)單的方法
if not new_messages:
return []
# 創(chuàng)建新話題
topic_id = str(uuid.uuid4())
topic_content = new_messages[0]['content'][:20] + "..." if len(new_messages[0]['content']) > 20 else new_messages[0]['content']
message_ids = [msg.get('message_id', str(uuid.uuid4())) for msg in new_messages]
# 保存消息到數(shù)據(jù)庫(kù)
for msg in new_messages:
if 'message_id' not in msg:
msg['message_id'] = str(uuid.uuid4())
chat_msg = ChatMessage(msg['message_id'], msg['role'], msg['content'])
db_manager.save_chat_message(session_id, chat_msg)
# 創(chuàng)建話題對(duì)象
topic = Topic(
topic_id=topic_id,
topic=topic_content,
summary=f"包含{len(new_messages)}條消息的話題",
message_ids=message_ids
)
# 保存話題到數(shù)據(jù)庫(kù)
db_manager.save_topic(topic)
# 返回話題信息
return [{
"topic_id": topic.topic_id,
"topic": topic.topic,
"summary": topic.summary,
"message_ids": topic.message_ids
}]
# 定義工具列表
tools = [
Tool.from_function(
func=get_history,
name="get_history",
description="獲取指定會(huì)話的聊天歷史記錄"
),
Tool.from_function(
func=select_topic_tool,
name="select_topic_tool",
description="根據(jù)topic_ids選擇話題,支持為每個(gè)topic指定是否只顯示摘要"
),
Tool.from_function(
func=compress_history_to_topic,
name="compress_history_to_topic",
description="將聊天記錄壓縮成話題"
)
]
# 創(chuàng)建提示模板
template = """Answer the following questions as best you can. You have access to the following tools:
{tools}
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {input}
Thought:{agent_scratchpad}"""
prompt = PromptTemplate.from_template(template)
# 創(chuàng)建智能體
history_retrieval_agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
qa_agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
topic_summary_agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
# 創(chuàng)建智能體執(zhí)行器
history_retrieval_agent = AgentExecutor(
agent=history_retrieval_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
qa_agent = AgentExecutor(
agent=qa_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
topic_summary_agent = AgentExecutor(
agent=topic_summary_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
def super_agent(user_input: str, session_id: str):
"""超級(jí)智能體主函數(shù)"""
if not session_id:
session_id = str(uuid.uuid4())
# 保存用戶輸入
user_message = ChatMessage(str(uuid.uuid4()), "user", user_input)
db_manager.save_chat_message(session_id, user_message)
# 1. 歷史信息檢索智能體
print("=== 歷史信息檢索智能體 ===")
# 檢索相關(guān)話題
all_topics = db_manager.get_all_topics()
topic_ids = [topic.topic_id for topic in all_topics]
if topic_ids:
# 為每個(gè)topic_id指定不同的summary_only值
summary_flags = [True, False] * (len(topic_ids) // 2 + 1)
summary_flags = summary_flags[:len(topic_ids)]
related_topics = select_topic_tool(topic_ids, summary_flags)
print(f"相關(guān)話題: {related_topics}")
else:
print("未找到相關(guān)話題")
related_topics = []
# 2. 使用檢測(cè)信息回答用戶問題智能體
print("=== 問答智能體 ===")
history = get_history(session_id)
print(f"會(huì)話歷史: {history}")
# 構(gòu)造問答上下文
qa_context = f"用戶問題: {user_input}\n相關(guān)話題: {related_topics}\n會(huì)話歷史: {history}"
try:
qa_response = qa_agent.invoke({
"input": f"基于以下信息回答用戶問題:\n{qa_context}\n\n用戶問題: {user_input}",
"tools": tools,
"tool_names": ", ".join([t.name for t in tools])
})
print(f"問答結(jié)果: {qa_response}")
except Exception as e:
print(f"問答智能體執(zhí)行出錯(cuò): {e}")
qa_response = {"output": "抱歉,我無法生成回答。"}
# 3. 處理歷史信息 話題總結(jié)智能體
print("=== 話題總結(jié)智能體 ===")
# 將當(dāng)前會(huì)話消息壓縮成話題
session_messages = get_history(session_id)
if session_messages:
new_topic = compress_history_to_topic(session_id, session_messages)
print(f"新話題: {new_topic}")
else:
print("沒有新消息需要壓縮成話題")
new_topic = []
return {
"session_id": session_id,
"qa_response": qa_response.get("output", qa_response.get("result", "無回答")),
"new_topics": new_topic
}
# 示例使用
if __name__ == "__main__":
result = super_agent("你好,能告訴我天氣怎么樣嗎?", "session_1")
print(f"最終結(jié)果: {result}")
并行
import os
import uuid
import sqlite3
import json
from datetime import datetime
from typing import List, Dict, Any, Union
from concurrent.futures import ThreadPoolExecutor, as_completed
from langchain.agents import AgentExecutor, create_react_agent
from langchain.prompts import PromptTemplate
from langchain.tools import Tool
# 為避免棄用警告,使用條件導(dǎo)入
try:
from langchain_openai import ChatOpenAI
except ImportError:
from langchain_community.chat_models import ChatOpenAI
# 配置連接到LM Studio的LLM
llm = ChatOpenAI(
model="qwen/qwen3-1.7b",
openai_api_key="lm-studio",
openai_api_base="http://127.0.0.1:1234/v1",
temperature=0.7
)
class Topic:
def __init__(self, topic_id: str, topic: str, summary: str, message_ids: List[str]):
self.topic_id = topic_id
self.topic = topic
self.summary = summary
self.message_ids = message_ids
class ChatMessage:
def __init__(self, message_id: str, role: str, content: str, timestamp: datetime = None):
self.message_id = message_id
self.role = role
self.content = content
self.timestamp = timestamp or datetime.now()
class DatabaseManager:
def __init__(self, db_path: str = "chat_topics.db"):
self.db_path = db_path
self.init_db()
def init_db(self):
"""初始化數(shù)據(jù)庫(kù)表"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 創(chuàng)建topics表
cursor.execute("""
CREATE TABLE IF NOT EXISTS topics (
topic_id TEXT PRIMARY KEY,
topic TEXT NOT NULL,
summary TEXT,
message_ids TEXT
)
""")
# 創(chuàng)建chat_messages表
cursor.execute("""
CREATE TABLE IF NOT EXISTS chat_messages (
message_id TEXT PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
conn.close()
def save_topic(self, topic: Topic):
"""保存話題"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute(
"INSERT OR REPLACE INTO topics (topic_id, topic, summary, message_ids) VALUES (?, ?, ?, ?)",
(topic.topic_id, topic.topic, topic.summary, json.dumps(topic.message_ids))
)
conn.commit()
conn.close()
def get_all_topics(self) -> List[Topic]:
"""獲取所有話題"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT topic_id, topic, summary, message_ids FROM topics")
rows = cursor.fetchall()
conn.close()
topics = []
for row in rows:
topic_id, topic_str, summary, message_ids_str = row
message_ids = json.loads(message_ids_str) if message_ids_str else []
topics.append(Topic(topic_id, topic_str, summary, message_ids))
return topics
def save_chat_message(self, session_id: str, message: ChatMessage):
"""保存聊天消息"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 使用INSERT OR IGNORE避免重復(fù)插入
cursor.execute(
"INSERT OR IGNORE INTO chat_messages (message_id, session_id, role, content) VALUES (?, ?, ?, ?)",
(message.message_id, session_id, message.role, message.content)
)
conn.commit()
conn.close()
def get_session_messages(self, session_id: str) -> List[ChatMessage]:
"""獲取會(huì)話消息"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute(
"SELECT message_id, role, content, timestamp FROM chat_messages WHERE session_id = ? ORDER BY timestamp",
(session_id,)
)
rows = cursor.fetchall()
conn.close()
messages = []
for row in rows:
message_id, role, content, timestamp = row
# 處理時(shí)間戳格式
if isinstance(timestamp, str):
try:
timestamp = datetime.fromisoformat(timestamp)
except ValueError:
timestamp = datetime.now()
messages.append(ChatMessage(message_id, role, content, timestamp))
return messages
# 創(chuàng)建數(shù)據(jù)庫(kù)管理器實(shí)例
db_manager = DatabaseManager()
def select_topic_tool(topic_ids: List[str], summary_only: Union[List[bool], bool] = False) -> List[Dict[str, Any]]:
"""根據(jù)topic_ids選擇話題的工具,支持為每個(gè)topic_id指定summary_only參數(shù)"""
try:
topics = db_manager.get_all_topics()
# 如果topic_ids為空,處理所有話題
if not topic_ids:
selected_topics = topics
# 如果summary_only是布爾值,則應(yīng)用于所有話題
if isinstance(summary_only, bool):
summary_flags = [summary_only] * len(selected_topics)
# 如果summary_only是列表,則按順序應(yīng)用
elif isinstance(summary_only, list):
summary_flags = summary_only
else:
summary_flags = [False] * len(selected_topics)
else:
# 根據(jù)指定的topic_ids篩選話題
selected_topics = [topic for topic in topics if topic.topic_id in topic_ids]
# 如果summary_only是布爾值,則應(yīng)用于所有選定話題
if isinstance(summary_only, bool):
summary_flags = [summary_only] * len(selected_topics)
# 如果summary_only是列表,則按順序應(yīng)用(如果長(zhǎng)度不夠則用False填充)
elif isinstance(summary_only, list):
summary_flags = summary_only[:len(selected_topics)]
# 如果summary_only列表長(zhǎng)度不足,用False填充
summary_flags.extend([False] * (len(selected_topics) - len(summary_flags)))
else:
summary_flags = [False] * len(selected_topics)
result = []
for i, topic in enumerate(selected_topics):
# 獲取對(duì)應(yīng)的summary_only標(biāo)志
show_summary_only = summary_flags[i] if i < len(summary_flags) else False
if show_summary_only:
result.append({
"topic_id": topic.topic_id,
"topic": topic.topic,
"summary": topic.summary
})
else:
result.append({
"topic_id": topic.topic_id,
"topic": topic.topic,
"summary": topic.summary,
"message_ids": topic.message_ids
})
return result
except Exception as e:
print(f"select_topic_tool執(zhí)行出錯(cuò): {e}")
return []
def get_history(session_id: str) -> List[Dict[str, Any]]:
"""獲取當(dāng)前會(huì)話的聊天記錄"""
try:
messages = db_manager.get_session_messages(session_id)
return [{"role": msg.role, "content": msg.content, "message_id": msg.message_id} for msg in messages]
except Exception as e:
print(f"get_history執(zhí)行出錯(cuò): {e}")
return []
def compress_history_to_topic(session_id: str, new_messages: List[Dict[str, Any]] = None) -> List[Dict[str, Any]]:
"""將新的聊天記錄壓縮成話題,并合并相似話題"""
try:
# 如果沒有提供new_messages,則從數(shù)據(jù)庫(kù)獲取會(huì)話消息
if new_messages is None:
new_messages = get_history(session_id)
# 這里應(yīng)該調(diào)用LLM來生成話題摘要,為了簡(jiǎn)化,我們使用簡(jiǎn)單的方法
if not new_messages:
return []
# 創(chuàng)建新話題
topic_id = str(uuid.uuid4())
topic_content = new_messages[0]['content'][:20] + "..." if len(new_messages[0]['content']) > 20 else new_messages[0]['content']
message_ids = [msg.get('message_id', str(uuid.uuid4())) for msg in new_messages]
# 保存消息到數(shù)據(jù)庫(kù)
for msg in new_messages:
if 'message_id' not in msg:
msg['message_id'] = str(uuid.uuid4())
chat_msg = ChatMessage(msg['message_id'], msg['role'], msg['content'])
db_manager.save_chat_message(session_id, chat_msg)
# 創(chuàng)建話題對(duì)象
new_topic = Topic(
topic_id=topic_id,
topic=topic_content,
summary=f"包含{len(new_messages)}條消息的話題",
message_ids=message_ids
)
# 檢查是否已存在相似話題
all_topics = db_manager.get_all_topics()
merged = False
for existing_topic in all_topics:
# 簡(jiǎn)單的相似性檢查:比較話題標(biāo)題的前幾個(gè)字符
if existing_topic.topic[:10] == topic_content[:10] and existing_topic.topic_id != topic_id:
# 合并到現(xiàn)有話題
existing_topic.message_ids.extend(message_ids)
existing_topic.summary = f"包含{len(existing_topic.message_ids)}條消息的話題"
db_manager.save_topic(existing_topic)
merged = True
break
# 如果沒有合并,則保存新話題
if not merged:
db_manager.save_topic(new_topic)
# 返回話題信息
return [{
"topic_id": new_topic.topic_id,
"topic": new_topic.topic,
"summary": new_topic.summary,
"message_ids": new_topic.message_ids
}]
except Exception as e:
print(f"compress_history_to_topic執(zhí)行出錯(cuò): {e}")
return []
# 定義工具列表
tools = [
Tool.from_function(
func=get_history,
name="get_history",
description="獲取指定會(huì)話的聊天歷史記錄"
),
Tool.from_function(
func=select_topic_tool,
name="select_topic_tool",
description="根據(jù)topic_ids選擇話題,支持為每個(gè)topic指定是否只顯示摘要"
),
Tool.from_function(
func=lambda session_id: compress_history_to_topic(session_id),
name="compress_history_to_topic",
description="將聊天記錄壓縮成話題"
)
]
# 創(chuàng)建提示模板
template = """Answer the following questions as best you can. You have access to the following tools:
{tools}
Use the following format:
Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question
Begin!
Question: {input}
Thought: {agent_scratchpad}"""
prompt = PromptTemplate.from_template(template)
# 創(chuàng)建智能體
history_retrieval_agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
qa_agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
topic_summary_agent = create_react_agent(llm=llm, tools=tools, prompt=prompt)
# 創(chuàng)建智能體執(zhí)行器
history_retrieval_agent = AgentExecutor(
agent=history_retrieval_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
qa_agent = AgentExecutor(
agent=qa_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
topic_summary_agent = AgentExecutor(
agent=topic_summary_agent,
tools=tools,
verbose=True,
handle_parsing_errors=True
)
def super_agent(user_input: str, session_id: str):
"""超級(jí)智能體主函數(shù)"""
if not session_id:
session_id = str(uuid.uuid4())
# 保存用戶輸入
user_message = ChatMessage(str(uuid.uuid4()), "user", user_input)
db_manager.save_chat_message(session_id, user_message)
# 1. 歷史信息檢索智能體(并行搜索)
print("=== 歷史信息檢索智能體(并行搜索) ===")
# 檢索相關(guān)話題
all_topics = db_manager.get_all_topics()
topic_ids = [topic.topic_id for topic in all_topics]
related_topics = []
if topic_ids:
# 將話題ID分組,每組10個(gè)
topic_groups = [topic_ids[i:i+10] for i in range(0, len(topic_ids), 10)]
# 并行執(zhí)行多個(gè)搜索任務(wù),每組話題作為一個(gè)任務(wù)
with ThreadPoolExecutor(max_workers=min(5, len(topic_groups))) as executor:
# 提交多個(gè)任務(wù)
future_to_topic = {}
# 為每組話題創(chuàng)建一個(gè)任務(wù)
for i, group in enumerate(topic_groups):
# 交替使用摘要和完整信息模式
summary_mode = (i % 2 == 0) # 偶數(shù)索引組使用摘要模式
future = executor.submit(select_topic_tool, group, summary_mode)
future_to_topic[future] = f"話題組{i}搜索({'摘要' if summary_mode else '完整'})"
# 任務(wù): 獲取會(huì)話歷史
future3 = executor.submit(get_history, session_id)
future_to_topic[future3] = "會(huì)話歷史"
# 收集并行任務(wù)結(jié)果
search_results = {}
for future in as_completed(future_to_topic):
task_name = future_to_topic[future]
try:
result = future.result()
search_results[task_name] = result
print(f"{task_name} 完成,結(jié)果數(shù): {len(result) if isinstance(result, list) else 'N/A'}")
except Exception as exc:
print(f'{task_name} 執(zhí)行時(shí)發(fā)生異常: {exc}')
search_results[task_name] = []
# 合并所有話題搜索結(jié)果
for task_name, result in search_results.items():
if "話題組" in task_name and isinstance(result, list):
related_topics.extend(result)
print(f"合并后相關(guān)話題數(shù): {len(related_topics)}")
else:
print("未找到相關(guān)話題")
related_topics = []
# 2. 使用檢測(cè)信息回答用戶問題智能體
print("=== 問答智能體 ===")
history = get_history(session_id)
print(f"會(huì)話歷史: {history}")
# 構(gòu)造問答上下文
qa_context = f"用戶問題: {user_input}\n相關(guān)話題: {related_topics}\n會(huì)話歷史: {history}"
try:
qa_response = qa_agent.invoke({
"input": f"基于以下信息回答用戶問題:\n{qa_context}\n\n用戶問題: {user_input}"
})
print(f"問答結(jié)果: {qa_response}")
except Exception as e:
print(f"問答智能體執(zhí)行出錯(cuò): {e}")
qa_response = {"output": "抱歉,我無法生成回答。"}
# 3. 處理歷史信息 話題總結(jié)智能體
print("=== 話題總結(jié)智能體 ===")
# 將當(dāng)前會(huì)話消息壓縮成話題
new_topic = compress_history_to_topic(session_id)
if new_topic:
print(f"新話題: {new_topic}")
else:
print("沒有新消息需要壓縮成話題")
# 確保qa_response不為None并且有合適的鍵
if qa_response is None:
qa_response = {"output": "抱歉,我無法生成回答。"}
# 處理可能缺少output或result鍵的情況
if "output" in qa_response:
qa_result = qa_response["output"]
elif "result" in qa_response:
qa_result = qa_response["result"]
else:
qa_result = "無回答"
return {
"session_id": session_id,
"qa_response": qa_result,
"new_topics": new_topic
}
# 示例使用
if __name__ == "__main__":
result = super_agent("你好,能告訴我天氣怎么樣嗎?", "session_1")
print(f"最終結(jié)果: {result}")
總結(jié)
本文詳細(xì)介紹了一個(gè)基于Python的智能對(duì)話系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)。系統(tǒng)利用LangChain框架和SQLite數(shù)據(jù)庫(kù),實(shí)現(xiàn)了對(duì)話記錄管理、話題識(shí)別和摘要生成等核心功能。通過多智能體協(xié)作架構(gòu),系統(tǒng)能夠高效地處理用戶查詢并管理對(duì)話歷史。
這種系統(tǒng)可以廣泛應(yīng)用于客服系統(tǒng)、個(gè)人知識(shí)管理、團(tuán)隊(duì)協(xié)作等多種場(chǎng)景,幫助用戶更好地組織和利用對(duì)話信息。希
提示:在實(shí)際部署時(shí),建議添加錯(cuò)誤處理、日志記錄和性能監(jiān)控等生產(chǎn)環(huán)境需要的功能,以確保系統(tǒng)的穩(wěn)定性和可維護(hù)性。
以上就是基于Python構(gòu)建一個(gè)智能對(duì)話系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Python智能對(duì)話系統(tǒng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)不規(guī)則圖形填充的思路
這篇文章主要介紹了Python實(shí)現(xiàn)不規(guī)則圖形填充的思路,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02
Python利用代碼計(jì)算2個(gè)坐標(biāo)之間的距離
這篇文章主要介紹了Python利用代碼計(jì)算2個(gè)坐標(biāo)之間的距離,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08
Python實(shí)現(xiàn)暴力匹配算法(字符串匹配)
本文主要介紹了Python實(shí)現(xiàn)暴力匹配算法,其主要思想是逐個(gè)字符地比較文本串和模式串,從文本串的每個(gè)可能的起始位置開始,依次檢查是否有匹配的子串,下面就來介紹 一下如何實(shí)現(xiàn)2023-09-09
Python 安裝 virturalenv 虛擬環(huán)境的教程詳解
這篇文章主要介紹了Python 安裝 virturalenv 虛擬環(huán)境的教程,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
Django rest framework jwt的使用方法詳解
這篇文章主要介紹了Django rest framework jwt的使用方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08
python多線程調(diào)用exit無法退出的解決方法
今天小編就為大家分享一篇python多線程調(diào)用exit無法退出的解決方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-02-02
python-pymysql獲取字段名稱-獲取內(nèi)容方式
這篇文章主要介紹了python-pymysql獲取字段名稱-獲取內(nèi)容方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05

