AI Agent 开发实战教程(三):记忆与数据库集成

## 目录

  • [1. Agent 的记忆系统设计](#1. Agent 的记忆系统设计)
  • [2. LangChain 记忆组件](#2. LangChain 记忆组件)
  • [3. 向量数据库:ChromaDB 实战](#3. 向量数据库:ChromaDB 实战)
  • [4. 向量数据库:Milvus 实战](#4. 向量数据库:Milvus 实战)
  • [5. 图数据库:Neo4j 实战](#5. 图数据库:Neo4j 实战)
  • [6. 结构化数据库:MySQL 实战](#6. 结构化数据库:MySQL 实战)
  • [7. 为 Agent 集成数据库工具](#7. 为 Agent 集成数据库工具)
  • [8. 小结与下一步](#8. 小结与下一步)

1. Agent 的记忆系统设计

1.1 为什么 Agent 需要数据库

在上一篇中,我们的 Agent 已经能够调用工具了。但它有一个致命缺陷:没有记忆
Agent 用户 Agent 用户 第一轮对话 第二轮对话(新会话) ❌ 忘了! 我叫张三,在北京工作 你好张三! 我叫什么名字? 抱歉,我不知道您的名字。

数据库为 Agent 提供三种记忆能力:

记忆类型 存储内容 存储方案 持久性
对话记忆 历史对话记录 关系型数据库(MySQL) 永久
语义记忆 知识、文档、经验 向量数据库(ChromaDB/Milvus) 永久
关系记忆 实体之间的关系 图数据库(Neo4j) 永久

1.2 三种数据库的定位

Agent 需要记住什么?
用户之前问过什么?
与这个问题相关的知识有哪些?
A 和 B 之间是什么关系?
MySQL

结构化记录
例如:查询订单历史、对话日志
向量数据库

语义搜索
例如:从知识库中检索相关文档
图数据库

关系查询
例如:张三的经理是谁?

1.3 安装依赖

bash 复制代码
# LangChain 核心组件
pip install langchain langchain-core langchain-openai langchain-community

# 向量数据库
pip install chromadb                    # 轻量级,适合入门
# pip install pymilvus                  # 企业级(需要 Milvus 服务)

# 图数据库
pip install neo4j langchain-neo4j       # Neo4j Python 驱动 + LangChain 集成

# 结构化数据库
pip install pymysql sqlalchemy langchain-community

# 嵌入模型(向量数据库需要)
pip install sentence-transformers       # 用于生成文本向量

2. LangChain 记忆组件

2.1 LangChain 记忆架构

LangChain 提供了多种记忆组件,从简单的对话缓冲到复杂的摘要记忆:
LangChain 记忆组件
RunnableWithMessageHistory
ChatMessageHistory
VectorStoreMemory
基于 Runnable 的记忆管理
支持会话隔离
内存中的消息历史
可持久化到数据库
向量存储记忆
语义检索历史对话

2.2 使用 ChatMessageHistory

python 复制代码
"""
memory/chat_history.py - LangChain 对话历史管理
"""
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
from langchain_community.chat_message_histories import ChatMessageHistory
from typing import Dict, List, Optional
import json


class InMemoryHistory(BaseChatMessageHistory):
    """
    内存中的对话历史存储

    适合:开发测试、单机应用
    不适合:生产环境(重启丢失)
    """

    def __init__(self):
        self._messages: List[BaseMessage] = []

    @property
    def messages(self) -> List[BaseMessage]:
        return self._messages

    def add_message(self, message: BaseMessage) -> None:
        self._messages.append(message)

    def add_user_message(self, message: str) -> None:
        self.add_message(HumanMessage(content=message))

    def add_ai_message(self, message: str) -> None:
        self.add_message(AIMessage(content=message))

    def clear(self) -> None:
        self._messages = []


# 会话存储(支持多用户)
class SessionHistoryStore:
    """
    会话历史管理器

    支持多用户、多会话的历史记录管理
    """

    def __init__(self):
        self._store: Dict[str, BaseChatMessageHistory] = {}

    def get_session_history(self, session_id: str) -> BaseChatMessageHistory:
        """获取或创建会话历史"""
        if session_id not in self._store:
            self._store[session_id] = InMemoryHistory()
        return self._store[session_id]

    def list_sessions(self) -> List[str]:
        """列出所有会话"""
        return list(self._store.keys())

    def clear_session(self, session_id: str) -> None:
        """清除指定会话"""
        if session_id in self._store:
            self._store[session_id].clear()


# === 使用示例 ===
if __name__ == "__main__":
    store = SessionHistoryStore()

    # 获取用户会话
    history = store.get_session_history("user_001")

    # 添加对话
    history.add_user_message("你好,我是张三")
    history.add_ai_message("你好张三!很高兴认识你。")
    history.add_user_message("我叫什么名字?")
    history.add_ai_message("你叫张三。")

    # 查看历史
    print("对话历史:")
    for msg in history.messages:
        print(f"  [{msg.type}]: {msg.content}")

2.3 使用 RunnableWithMessageHistory

python 复制代码
"""
memory/runnable_with_history.py - 带记忆的 Chain
"""
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from typing import Dict, List
import os


# 会话存储
store: Dict[str, ChatMessageHistory] = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
    """获取会话历史的回调函数"""
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]


def create_chat_with_memory():
    """
    创建带记忆的对话 Chain

    使用 LCEL 语法 + RunnableWithMessageHistory
    """
    # 1. 创建 Prompt(包含历史消息占位符)
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个友好的助手,能够记住用户的信息。"),
        MessagesPlaceholder(variable_name="history"),  # 历史消息占位符
        ("human", "{input}"),
    ])

    # 2. 创建模型
    model = ChatOpenAI(
        model="deepseek-chat",
        base_url="https://api.deepseek.com/v1",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        temperature=0.7,
    )

    # 3. 创建基础 Chain
    chain = prompt | model | StrOutputParser()

    # 4. 包装为带记忆的 Chain
    chain_with_history = RunnableWithMessageHistory(
        chain,
        get_session_history,
        input_messages_key="input",
        history_messages_key="history",
    )

    return chain_with_history


# === 使用示例 ===
if __name__ == "__main__":
    chain = create_chat_with_memory()

    # 配置会话 ID
    config = {"configurable": {"session_id": "user_zhangsan"}}

    # 第一轮对话
    print("=== 第一轮对话 ===")
    response1 = chain.invoke({"input": "你好,我叫张三,在北京工作"}, config=config)
    print(f"助手: {response1}")

    # 第二轮对话(Agent 记住了!)
    print("\n=== 第二轮对话 ===")
    response2 = chain.invoke({"input": "我叫什么名字?在哪里工作?"}, config=config)
    print(f"助手: {response2}")

    # 切换用户(新会话)
    print("\n=== 切换用户 ===")
    config2 = {"configurable": {"session_id": "user_lisi"}}
    response3 = chain.invoke({"input": "我叫什么名字?"}, config=config2)
    print(f"助手: {response3}")  # 不知道,因为是新会话

2.4 记忆流程图

记忆管理
用户输入
获取会话历史
SessionHistory
构建 Prompt

系统消息 + 历史消息 + 用户输入
LLM 处理
生成回复
保存到历史
返回结果


3. 向量数据库:ChromaDB 实战

3.1 什么是向量数据库

向量数据库存储的是文本的数学表示(向量/Embedding),而不是文本本身。它通过"语义相似度"来搜索,而不是关键词匹配。
向量搜索
查询: 手机退货
语义理解
向量相似度计算
匹配: 7天无理由

退换货规定 ✅
传统搜索
查询: 手机退货
关键词匹配
匹配: 手机、退货
可能漏掉

退换货政策

3.2 LangChain + ChromaDB 集成

python 复制代码
"""
vector_store/chroma_langchain.py - LangChain ChromaDB 集成
安装: pip install chromadb langchain-community sentence-transformers
"""
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document
from langchain_core.vectorstores import VectorStoreRetriever
from typing import List, Dict, Optional
import os


class ChromaVectorStore:
    """
    基于 LangChain 的 ChromaDB 向量存储

    特性:
    - 使用 LangChain 标准接口
    - 支持中文 Embedding 模型
    - 支持 LCEL 集成
    """

    def __init__(
        self,
        collection_name: str = "default",
        persist_dir: str = "./chroma_db",
        embedding_model: str = "BAAI/bge-small-zh-v1.5",
    ):
        """
        初始化向量存储

        Args:
            collection_name: 集合名称
            persist_dir: 持久化目录
            embedding_model: Embedding 模型名称
        """
        # 初始化 Embedding 模型
        self.embeddings = HuggingFaceEmbeddings(
            model_name=embedding_model,
            model_kwargs={"device": "cpu"},
            encode_kwargs={"normalize_embeddings": True},
        )

        self.persist_dir = persist_dir
        self.collection_name = collection_name

        # 创建或加载向量存储
        self.vectorstore = Chroma(
            collection_name=collection_name,
            embedding_function=self.embeddings,
            persist_directory=persist_dir,
        )
        print(f"[ChromaDB] 集合 '{collection_name}' 已就绪")

    # ========================
    # CREATE: 添加文档
    # ========================

    def add_documents(
        self,
        texts: List[str],
        metadatas: Optional[List[Dict]] = None,
        ids: Optional[List[str]] = None,
    ):
        """
        添加文档到向量库

        Args:
            texts: 文本列表
            metadatas: 元数据列表
            ids: 文档 ID 列表
        """
        # 转换为 LangChain Document
        documents = []
        for i, text in enumerate(texts):
            doc = Document(
                page_content=text,
                metadata=metadatas[i] if metadatas else {},
            )
            documents.append(doc)

        # 添加到向量存储
        self.vectorstore.add_documents(documents, ids=ids)
        print(f"[ChromaDB] 添加了 {len(documents)} 个文档")

    def add_texts(self, texts: List[str], metadatas: Optional[List[Dict]] = None):
        """添加文本(简化接口)"""
        self.vectorstore.add_texts(texts, metadatas=metadatas)

    # ========================
    # READ: 查询文档
    # ========================

    def search(self, query: str, k: int = 5) -> List[Dict]:
        """
        语义搜索

        Args:
            query: 查询文本
            k: 返回数量

        Returns:
            [{"content": "...", "metadata": {...}, "score": 0.95}, ...]
        """
        results = self.vectorstore.similarity_search_with_score(query, k=k)

        formatted = []
        for doc, score in results:
            formatted.append({
                "content": doc.page_content,
                "metadata": doc.metadata,
                "score": float(score),
            })

        return formatted

    def search_with_threshold(
        self,
        query: str,
        k: int = 5,
        score_threshold: float = 0.8,
    ) -> List[Dict]:
        """带阈值过滤的搜索"""
        results = self.search(query, k)
        return [r for r in results if r["score"] >= score_threshold]

    # ========================
    # DELETE: 删除文档
    # ========================

    def delete_by_ids(self, ids: List[str]):
        """根据 ID 删除文档"""
        self.vectorstore.delete(ids=ids)
        print(f"[ChromaDB] 删除了 {len(ids)} 个文档")

    # ========================
    # RETRIEVER: 获取检索器
    # ========================

    def as_retriever(
        self,
        search_type: str = "similarity",
        search_kwargs: Optional[Dict] = None,
    ) -> VectorStoreRetriever:
        """
        获取 LangChain Retriever

        Args:
            search_type: 搜索类型
                - "similarity": 相似度搜索
                - "mmr": 最大边际相关性(多样性)
                - "similarity_score_threshold": 带阈值过滤
            search_kwargs: 搜索参数
                - {"k": 5}
                - {"k": 5, "fetch_k": 20, "lambda_mult": 0.5}  # MMR
                - {"k": 5, "score_threshold": 0.8}  # 阈值
        """
        kwargs = search_kwargs or {"k": 5}
        return self.vectorstore.as_retriever(
            search_type=search_type,
            search_kwargs=kwargs,
        )

    # ========================
    # UTILITY: 工具方法
    # ========================

    def count(self) -> int:
        """获取文档数量"""
        return self.vectorstore._collection.count()


# === 使用示例 ===
if __name__ == "__main__":
    store = ChromaVectorStore(
        collection_name="demo",
        persist_dir="./demo_chroma_db",
    )

    # 1. 添加文档
    store.add_documents(
        texts=[
            "Python 是一种广泛使用的高级编程语言,以简洁易读著称。",
            "Java 是一种面向对象的编程语言,广泛用于企业级开发。",
            "机器学习是人工智能的一个分支,让计算机从数据中学习。",
            "深度学习使用多层神经网络来学习数据的表示。",
            "自然语言处理(NLP)让计算机理解和生成人类语言。",
        ],
        metadatas=[
            {"category": "编程", "language": "python"},
            {"category": "编程", "language": "java"},
            {"category": "AI", "topic": "ML"},
            {"category": "AI", "topic": "DL"},
            {"category": "AI", "topic": "NLP"},
        ],
        ids=["doc1", "doc2", "doc3", "doc4", "doc5"],
    )

    # 2. 语义搜索
    print("\n--- 搜索: '什么是深度学习' ---")
    results = store.search("什么是深度学习", k=3)
    for r in results:
        print(f"  [{r['score']:.3f}] {r['content'][:50]}...")

    # 3. 获取 Retriever(用于 RAG)
    retriever = store.as_retriever(search_kwargs={"k": 3})
    docs = retriever.invoke("编程语言有哪些?")
    print(f"\n--- Retriever 结果 ---")
    for doc in docs:
        print(f"  {doc.page_content[:50]}...")

3.3 使用 LangChain Embeddings

python 复制代码
"""
vector_store/embeddings.py - LangChain Embedding 模型配置
"""
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_openai import OpenAIEmbeddings
from typing import List
import os


def get_embeddings(provider: str = "bge"):
    """
    获取 Embedding 模型

    Args:
        provider: 模型提供商
            - "bge": BGE 中文模型(推荐)
            - "openai": OpenAI Embeddings
            - "deepseek": DeepSeek Embeddings
    """
    if provider == "bge":
        # BGE 中文模型(本地运行,免费)
        return HuggingFaceEmbeddings(
            model_name="BAAI/bge-small-zh-v1.5",
            model_kwargs={"device": "cpu"},
            encode_kwargs={"normalize_embeddings": True},
        )

    elif provider == "openai":
        # OpenAI Embeddings(需要 API Key)
        return OpenAIEmbeddings(
            model="text-embedding-3-small",
            openai_api_key=os.getenv("OPENAI_API_KEY"),
        )

    elif provider == "deepseek":
        # DeepSeek 兼容 OpenAI 接口
        return OpenAIEmbeddings(
            model="text-embedding-3-small",
            openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
            openai_api_base="https://api.deepseek.com/v1",
        )

    else:
        raise ValueError(f"未知的 Embedding 提供商: {provider}")


# === 使用示例 ===
if __name__ == "__main__":
    embeddings = get_embeddings("bge")

    # 嵌入单个文本
    vector = embeddings.embed_query("什么是机器学习?")
    print(f"向量维度: {len(vector)}")
    print(f"前 5 维: {vector[:5]}")

    # 批量嵌入
    texts = ["Python 是编程语言", "Java 是编程语言"]
    vectors = embeddings.embed_documents(texts)
    print(f"批量嵌入数量: {len(vectors)}")

4. 向量数据库:Milvus 实战

4.1 ChromaDB vs Milvus

维度 ChromaDB Milvus
定位 轻量级,嵌入式 企业级,分布式
数据规模 < 100 万向量 10 亿+ 向量
部署 pip install,零配置 需要 Docker/K8s
性能 适合开发和小规模 GPU 加速,高性能
适合场景 原型开发、个人项目 生产环境、大规模应用

4.2 LangChain + Milvus 集成

python 复制代码
"""
vector_store/milvus_langchain.py - LangChain Milvus 集成
安装: pip install pymilvus langchain-community
前置条件: 需要 Milvus 服务运行
"""
from langchain_community.vectorstores import Milvus
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document
from typing import List, Dict, Optional
import os


class MilvusVectorStore:
    """
    基于 LangChain 的 Milvus 向量存储

    特性:
    - 支持大规模数据(10亿+向量)
    - 支持元数据过滤
    - 支持分布式部署
    """

    def __init__(
        self,
        collection_name: str = "default",
        connection_args: Optional[Dict] = None,
        embedding_model: str = "BAAI/bge-small-zh-v1.5",
    ):
        """
        初始化 Milvus 向量存储

        Args:
            collection_name: 集合名称
            connection_args: 连接参数
                {"host": "localhost", "port": "19530"}
            embedding_model: Embedding 模型
        """
        self.connection_args = connection_args or {
            "host": "localhost",
            "port": "19530",
        }

        # 初始化 Embedding
        self.embeddings = HuggingFaceEmbeddings(
            model_name=embedding_model,
            model_kwargs={"device": "cpu"},
            encode_kwargs={"normalize_embeddings": True},
        )

        self.collection_name = collection_name
        self.vectorstore: Optional[Milvus] = None

    def init_with_documents(self, documents: List[Document]):
        """
        使用文档初始化向量存储(创建新集合)

        Args:
            documents: LangChain Document 列表
        """
        self.vectorstore = Milvus.from_documents(
            documents=documents,
            embedding=self.embeddings,
            collection_name=self.collection_name,
            connection_args=self.connection_args,
        )
        print(f"[Milvus] 集合 '{self.collection_name}' 已创建")

    def connect_existing(self):
        """连接到现有集合"""
        self.vectorstore = Milvus(
            embedding_function=self.embeddings,
            collection_name=self.collection_name,
            connection_args=self.connection_args,
        )
        print(f"[Milvus] 已连接到集合 '{self.collection_name}'")

    # ========================
    # CREATE: 添加文档
    # ========================

    def add_documents(self, documents: List[Document]):
        """添加文档"""
        if not self.vectorstore:
            raise RuntimeError("请先初始化向量存储")
        self.vectorstore.add_documents(documents)

    def add_texts(self, texts: List[str], metadatas: Optional[List[Dict]] = None):
        """添加文本"""
        if not self.vectorstore:
            raise RuntimeError("请先初始化向量存储")
        self.vectorstore.add_texts(texts, metadatas=metadatas)

    # ========================
    # READ: 搜索
    # ========================

    def search(self, query: str, k: int = 5) -> List[Dict]:
        """语义搜索"""
        if not self.vectorstore:
            raise RuntimeError("请先初始化向量存储")

        results = self.vectorstore.similarity_search_with_score(query, k=k)

        return [
            {
                "content": doc.page_content,
                "metadata": doc.metadata,
                "score": float(score),
            }
            for doc, score in results
        ]

    def search_with_filter(
        self,
        query: str,
        filter_expr: str,
        k: int = 5,
    ) -> List[Dict]:
        """
        带元数据过滤的搜索

        Args:
            query: 查询文本
            filter_expr: 过滤表达式
                例如: 'category == "AI"'
        """
        if not self.vectorstore:
            raise RuntimeError("请先初始化向量存储")

        results = self.vectorstore.similarity_search_with_score(
            query,
            k=k,
            expr=filter_expr,
        )

        return [
            {
                "content": doc.page_content,
                "metadata": doc.metadata,
                "score": float(score),
            }
            for doc, score in results
        ]

    # ========================
    # RETRIEVER
    # ========================

    def as_retriever(self, search_kwargs: Optional[Dict] = None):
        """获取 Retriever"""
        if not self.vectorstore:
            raise RuntimeError("请先初始化向量存储")
        return self.vectorstore.as_retriever(
            search_kwargs=search_kwargs or {"k": 5}
        )


# === 使用示例 ===
if __name__ == "__main__":
    # 注意:需要先启动 Milvus 服务
    # docker run -d --name milvus -p 19530:19530 milvusdb/milvus:latest

    store = MilvusVectorStore(
        collection_name="demo",
        connection_args={"host": "localhost", "port": "19530"},
    )

    # 初始化并添加文档
    documents = [
        Document(page_content="人工智能是计算机科学的一个分支", metadata={"category": "AI"}),
        Document(page_content="机器学习是实现人工智能的一种方法", metadata={"category": "ML"}),
        Document(page_content="深度学习是机器学习的一个子领域", metadata={"category": "DL"}),
    ]

    store.init_with_documents(documents)

    # 搜索
    results = store.search("AI 和机器学习有什么关系?", k=3)
    for r in results:
        print(f"  [{r['score']:.3f}] {r['content']}")

5. 图数据库:Neo4j 实战

5.1 为什么 Agent 需要图数据库

图数据库擅长处理实体之间的关系
图数据库 Neo4j
查询: 张三的经理的部门的所有项目
一次图遍历
毫秒级响应 ✅
关系型数据库 MySQL
查询: 张三的经理的部门的所有项目
需要 3 次 JOIN
性能较差

5.2 安装 Neo4j

bash 复制代码
# 方式一:Docker(推荐)
docker run -d \
  --name neo4j \
  -p 7474:7474 \
  -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/your-password \
  neo4j:latest

# 方式二:Desktop
# 下载 Neo4j Desktop: https://neo4j.com/download/

5.3 LangChain + Neo4j 集成

python 复制代码
"""
graph_store/neo4j_langchain.py - LangChain Neo4j 集成
安装: pip install neo4j langchain-community
"""
from langchain_community.graphs import Neo4jGraph
from langchain_community.chains.graph_qa.cypher import GraphCypherQAChain
from langchain_openai import ChatOpenAI
from typing import List, Dict, Optional
import os


class Neo4jGraphStore:
    """
    基于 LangChain 的 Neo4j 图数据库操作

    核心概念:
    - Node(节点): 实体,如 Person、Department
    - Relationship(关系): 节点之间的连接
    - Property(属性): 节点或关系上的键值对
    """

    def __init__(
        self,
        url: str = "bolt://localhost:7687",
        username: str = "neo4j",
        password: str = "your-password",
    ):
        """
        初始化 Neo4j 连接

        Args:
            url: Neo4j 连接 URL
            username: 用户名
            password: 密码
        """
        self.graph = Neo4jGraph(
            url=url,
            username=username,
            password=password,
        )
        print(f"[Neo4j] 已连接到 {url}")

    # ========================
    # CREATE: 创建节点和关系
    # ========================

    def create_node(self, label: str, properties: Dict) -> None:
        """
        创建节点

        Args:
            label: 节点标签(类型)
            properties: 节点属性
        """
        # 构建 Cypher 语句
        props_str = ", ".join(f"{k}: ${k}" for k in properties.keys())
        query = f"CREATE (n:{label} {{{props_str}}})"

        self.graph.query(query, params=properties)
        print(f"[Neo4j] 创建节点: {label} {properties}")

    def create_relationship(
        self,
        from_label: str,
        from_props: Dict,
        to_label: str,
        to_props: Dict,
        rel_type: str,
        rel_props: Optional[Dict] = None,
    ) -> None:
        """
        创建关系

        Args:
            from_label: 起始节点标签
            from_props: 起始节点匹配属性
            to_label: 目标节点标签
            to_props: 目标节点匹配属性
            rel_type: 关系类型
            rel_props: 关系属性(可选)
        """
        from_match = " AND ".join(f"a.{k} = $from_{k}" for k in from_props)
        to_match = " AND ".join(f"b.{k} = $to_{k}" for k in to_props)

        rel_prop_str = ""
        params = {}
        if rel_props:
            rel_prop_str = " {" + ", ".join(f"{k}: $rel_{k}" for k in rel_props) + "}"
            for k, v in rel_props.items():
                params[f"rel_{k}"] = v

        for k, v in from_props.items():
            params[f"from_{k}"] = v
        for k, v in to_props.items():
            params[f"to_{k}"] = v

        query = f"""
        MATCH (a:{from_label} {{{from_match}}})
        MATCH (b:{to_label} {{{to_match}}})
        CREATE (a)-[:{rel_type}{rel_prop_str}]->(b)
        """

        self.graph.query(query, params=params)
        print(f"[Neo4j] 创建关系: ({from_label})-[:{rel_type}]->({to_label})")

    # ========================
    # READ: 查询
    # ========================

    def query(self, cypher: str, params: Optional[Dict] = None) -> List[Dict]:
        """
        执行 Cypher 查询

        Args:
            cypher: Cypher 查询语句
            params: 查询参数

        Returns:
            查询结果列表
        """
        return self.graph.query(cypher, params=params)

    def find_nodes(self, label: str, properties: Optional[Dict] = None) -> List[Dict]:
        """查找节点"""
        if properties:
            where = " AND ".join(f"n.{k} = ${k}" for k in properties)
            query = f"MATCH (n:{label}) WHERE {where} RETURN n"
            return self.graph.query(query, params=properties)
        else:
            query = f"MATCH (n:{label}) RETURN n LIMIT 50"
            return self.graph.query(query)

    def get_neighbors(
        self,
        label: str,
        name: str,
        rel_type: Optional[str] = None,
    ) -> List[Dict]:
        """
        获取节点的邻居

        Args:
            label: 节点标签
            name: 节点名称
            rel_type: 关系类型(可选)
        """
        if rel_type:
            query = f"""
            MATCH (a:{label} {{name: $name}})-[:{rel_type}]-(b)
            RETURN b
            """
        else:
            query = f"""
            MATCH (a:{label} {{name: $name}})--(b)
            RETURN b
            """

        return self.graph.query(query, params={"name": name})

    # ========================
    # UPDATE: 更新
    # ========================

    def update_node(
        self,
        label: str,
        match_props: Dict,
        update_props: Dict,
    ) -> None:
        """更新节点属性"""
        match_str = " AND ".join(f"n.{k} = $match_{k}" for k in match_props)
        set_str = ", ".join(f"n.{k} = $update_{k}" for k in update_props)

        params = {}
        for k, v in match_props.items():
            params[f"match_{k}"] = v
        for k, v in update_props.items():
            params[f"update_{k}"] = v

        query = f"""
        MATCH (n:{label} {{{match_str}}})
        SET {set_str}
        """

        self.graph.query(query, params=params)
        print(f"[Neo4j] 更新节点: {label}")

    # ========================
    # DELETE: 删除
    # ========================

    def delete_node(self, label: str, properties: Dict) -> None:
        """删除节点(同时删除关联关系)"""
        match_str = " AND ".join(f"n.{k} = ${k}" for k in properties)
        query = f"MATCH (n:{label} {{{match_str}}}) DETACH DELETE n"
        self.graph.query(query, params=properties)
        print(f"[Neo4j] 删除节点: {label}")

    def clear_all(self) -> None:
        """清空数据库(危险操作!)"""
        self.graph.query("MATCH (n) DETACH DELETE n")
        print("[Neo4j] 数据库已清空")

    # ========================
    # GRAPH QA: 自然语言查询
    # ========================

    def create_qa_chain(self, llm=None):
        """
        创建图数据库问答 Chain

        支持自然语言查询图数据库
        """
        if llm is None:
            llm = ChatOpenAI(
                model="deepseek-chat",
                base_url="https://api.deepseek.com/v1",
                api_key=os.getenv("DEEPSEEK_API_KEY"),
                temperature=0,
            )

        # 刷新图结构信息
        self.graph.refresh_schema()

        # 创建 QA Chain
        chain = GraphCypherQAChain.from_llm(
            llm=llm,
            graph=self.graph,
            verbose=True,
            return_intermediate_steps=True,
        )

        return chain


# === 使用示例 ===
if __name__ == "__main__":
    store = Neo4jGraphStore(password="your-password")

    # 清空旧数据
    store.clear_all()

    # 1. 创建节点
    store.create_node("Person", {"name": "张三", "role": "工程师", "age": 30})
    store.create_node("Person", {"name": "李四", "role": "经理", "age": 40})
    store.create_node("Department", {"name": "技术部"})
    store.create_node("Project", {"name": "AI助手", "status": "进行中"})

    # 2. 创建关系
    store.create_relationship(
        "Person", {"name": "张三"},
        "Department", {"name": "技术部"},
        "WORKS_IN"
    )
    store.create_relationship(
        "Person", {"name": "李四"},
        "Department", {"name": "技术部"},
        "MANAGES"
    )
    store.create_relationship(
        "Person", {"name": "张三"},
        "Project", {"name": "AI助手"},
        "ASSIGNED_TO"
    )

    # 3. 查询
    print("\n--- 查询: 技术部的所有员工 ---")
    results = store.find_nodes("Department", {"name": "技术部"})
    for r in results:
        print(f"  {r}")

    print("\n--- 查询: 张三认识谁 ---")
    neighbors = store.get_neighbors("Person", "张三")
    for n in neighbors:
        print(f"  {n}")

    # 4. 自然语言查询
    print("\n--- 自然语言查询 ---")
    qa_chain = store.create_qa_chain()
    result = qa_chain.invoke({"query": "技术部有哪些人?"})
    print(f"结果: {result['result']}")

5.4 图数据库查询流程

LangChain
Neo4j
自然语言查询
LLM 生成 Cypher
执行 Cypher 查询
获取查询结果
LLM 生成自然语言回答
返回结果


6. 结构化数据库:MySQL 实战

6.1 安装 MySQL

bash 复制代码
# Docker 方式
docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=your-password \
  -e MYSQL_DATABASE=agent_db \
  mysql:8.0

6.2 LangChain SQL 集成

python 复制代码
"""
db/mysql_langchain.py - LangChain SQL 数据库集成
安装: pip install pymysql sqlalchemy langchain-community
"""
from langchain_community.utilities import SQLDatabase
from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool
from langchain_openai import ChatOpenAI
from langchain.chains import create_sql_query_chain
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from typing import List, Dict, Optional
import os


class MySQLStore:
    """
    基于 LangChain 的 MySQL 数据库操作

    特性:
    - 使用 LangChain SQLDatabase 统一接口
    - 支持自然语言转 SQL
    - 支持安全查询
    """

    def __init__(
        self,
        host: str = "localhost",
        port: int = 3306,
        user: str = "root",
        password: str = "your-password",
        database: str = "agent_db",
    ):
        """
        初始化 MySQL 连接

        Args:
            host: 主机地址
            port: 端口
            user: 用户名
            password: 密码
            database: 数据库名
        """
        connection_string = f"mysql+pymysql://{user}:{password}@{host}:{port}/{database}?charset=utf8mb4"

        self.db = SQLDatabase.from_uri(
            connection_string,
            include_tables=[],  # 空列表表示包含所有表
            sample_rows_in_table_info=3,  # 在表信息中包含示例行
        )

        print(f"[MySQL] 已连接到 {host}:{port}/{database}")

    # ========================
    # SCHEMA: 获取表结构
    # ========================

    def get_table_info(self) -> str:
        """获取所有表的结构信息"""
        return self.db.get_table_info()

    def get_table_names(self) -> List[str]:
        """获取所有表名"""
        return self.db.get_usable_table_names()

    # ========================
    # QUERY: 执行查询
    # ========================

    def run_query(self, sql: str) -> str:
        """
        执行 SQL 查询(只读)

        Args:
            sql: SQL 查询语句

        Returns:
            查询结果(字符串格式)
        """
        return self.db.run(sql)

    def run_query_dict(self, sql: str) -> List[Dict]:
        """执行查询并返回字典列表"""
        result = self.db.run(sql, include_columns=True)
        # 解析结果为字典列表
        return result

    # ========================
    # WRITE: 执行写入
    # ========================

    def execute_write(self, sql: str) -> None:
        """
        执行写入操作(INSERT/UPDATE/DELETE)

        注意:需要使用原生连接
        """
        import pymysql

        conn = pymysql.connect(
            host="localhost",
            user="root",
            password="your-password",
            database="agent_db",
            charset="utf8mb4",
        )
        try:
            with conn.cursor() as cursor:
                cursor.execute(sql)
                conn.commit()
        finally:
            conn.close()

    # ========================
    # NATURAL LANGUAGE: 自然语言查询
    # ========================

    def create_nl_query_chain(self, llm=None):
        """
        创建自然语言转 SQL 的 Chain

        用户可以用自然语言查询数据库
        """
        if llm is None:
            llm = ChatOpenAI(
                model="deepseek-chat",
                base_url="https://api.deepseek.com/v1",
                api_key=os.getenv("DEEPSEEK_API_KEY"),
                temperature=0,
            )

        # 创建 SQL 查询生成 Chain
        write_query = create_sql_query_chain(llm, self.db)

        # 创建执行查询的工具
        execute_query = QuerySQLDataBaseTool(db=self.db)

        # 组合成完整 Chain
        answer_prompt = PromptTemplate.from_template(
            """根据以下用户问题、SQL 查询和查询结果,给出自然语言回答:

用户问题: {question}
SQL 查询: {query}
查询结果: {result}

回答:"""
        )

        chain = (
            RunnablePassthrough.assign(query=write_query).assign(
                result=itemgetter("query") | execute_query
            )
            | answer_prompt
            | llm
            | StrOutputParser()
        )

        return chain


# 需要导入 itemgetter
from operator import itemgetter


# === 使用示例 ===
if __name__ == "__main__":
    store = MySQLStore(password="your-password")

    # 1. 查看表结构
    print("=== 表结构 ===")
    print(store.get_table_info())

    # 2. 查看表名
    print("\n=== 表名 ===")
    print(store.get_table_names())

    # 3. 执行查询
    print("\n=== 查询结果 ===")
    result = store.run_query("SELECT * FROM users LIMIT 5")
    print(result)

    # 4. 自然语言查询
    print("\n=== 自然语言查询 ===")
    chain = store.create_nl_query_chain()
    answer = chain.invoke({"question": "有多少用户?"})
    print(f"回答: {answer}")

6.3 使用 SQLAlchemy ORM

python 复制代码
"""
db/sqlalchemy_models.py - SQLAlchemy ORM 模型
安装: pip install sqlalchemy pymysql
"""
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Text, Float
from sqlalchemy.orm import declarative_base, sessionmaker
from datetime import datetime

# 创建引擎
engine = create_engine(
    "mysql+pymysql://root:your-password@localhost/agent_db?charset=utf8mb4"
)
Session = sessionmaker(bind=engine)
Base = declarative_base()


# === 定义模型 ===
class User(Base):
    """用户模型"""
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(100), nullable=False)
    email = Column(String(200), unique=True)
    role = Column(String(50), default="user")
    created_at = Column(DateTime, default=datetime.now)


class Conversation(Base):
    """对话记录模型"""
    __tablename__ = "conversations"

    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(Integer, nullable=False)
    session_id = Column(String(100))
    role = Column(String(20), nullable=False)  # user / assistant
    content = Column(Text, nullable=False)
    created_at = Column(DateTime, default=datetime.now)


class AgentLog(Base):
    """Agent 执行日志"""
    __tablename__ = "agent_logs"

    id = Column(Integer, primary_key=True, autoincrement=True)
    user_id = Column(Integer)
    session_id = Column(String(100))
    action_type = Column(String(50))  # tool_call / response / error
    tool_name = Column(String(100))
    input_data = Column(Text)
    output_data = Column(Text)
    duration_ms = Column(Float)
    created_at = Column(DateTime, default=datetime.now)


# 建表
Base.metadata.create_all(engine)


# === 使用示例 ===
if __name__ == "__main__":
    session = Session()

    # CREATE
    user = User(name="张三", email="zhangsan@example.com", role="engineer")
    session.add(user)
    session.commit()
    print(f"创建用户: ID={user.id}")

    # READ
    all_users = session.query(User).all()
    engineers = session.query(User).filter(User.role == "engineer").all()
    user = session.query(User).filter_by(name="张三").first()

    # UPDATE
    user.role = "senior_engineer"
    session.commit()

    # DELETE
    session.delete(user)
    session.commit()

    session.close()

7. 为 Agent 集成数据库工具

7.1 统一数据库工具集

python 复制代码
"""
tools/db_tools.py - Agent 数据库工具集
将数据库操作封装为 LangChain Tool
"""
from langchain_core.tools import tool
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings
from typing import Optional, List, Dict
import json
import os


class DatabaseToolkit:
    """
    数据库工具集

    为 Agent 提供统一的数据库操作工具
    """

    def __init__(self):
        # 初始化向量存储
        self.embeddings = HuggingFaceEmbeddings(
            model_name="BAAI/bge-small-zh-v1.5",
            model_kwargs={"device": "cpu"},
        )

        self.vectorstore = Chroma(
            collection_name="knowledge",
            embedding_function=self.embeddings,
            persist_directory="./chroma_db",
        )

    def get_tools(self):
        """获取所有工具"""
        return [
            self.search_knowledge,
            self.save_memory,
            self.query_user_info,
        ]

    @tool
    def search_knowledge(self, query: str, category: Optional[str] = None) -> str:
        """
        搜索知识库,查找与问题相关的文档和知识。

        Args:
            query: 搜索关键词或问题
            category: 限定搜索类别(可选)

        Returns:
            相关文档内容
        """
        # 构建过滤条件
        filter_dict = None
        if category:
            filter_dict = {"category": category}

        # 搜索
        if filter_dict:
            results = self.vectorstore.similarity_search_with_score(
                query, k=5, filter=filter_dict
            )
        else:
            results = self.vectorstore.similarity_search_with_score(query, k=5)

        if not results:
            return "未找到相关信息"

        # 格式化结果
        formatted = []
        for doc, score in results:
            formatted.append({
                "content": doc.page_content,
                "metadata": doc.metadata,
                "score": round(float(score), 3),
            })

        return json.dumps(formatted, ensure_ascii=False, indent=2)

    @tool
    def save_memory(self, content: str, category: str = "general") -> str:
        """
        保存重要信息到记忆中,用于后续对话。

        Args:
            content: 要记住的内容
            category: 分类标签

        Returns:
            保存结果
        """
        import uuid

        doc_id = f"memory_{uuid.uuid4().hex[:8]}"

        self.vectorstore.add_texts(
            texts=[content],
            metadatas=[{"type": "memory", "category": category}],
            ids=[doc_id],
        )

        return f"已记住: {content[:50]}..."

    @tool
    def query_user_info(self, user_id: str) -> str:
        """
        查询用户信息。

        Args:
            user_id: 用户 ID

        Returns:
            用户信息
        """
        # 模拟查询(实际应连接 MySQL)
        mock_data = {
            "user_001": {"name": "张三", "role": "工程师", "department": "技术部"},
            "user_002": {"name": "李四", "role": "经理", "department": "技术部"},
        }

        if user_id in mock_data:
            return json.dumps(mock_data[user_id], ensure_ascii=False)
        return f"未找到用户: {user_id}"


# === 使用示例 ===
if __name__ == "__main__":
    from langchain_openai import ChatOpenAI
    from langchain.agents import create_tool_calling_agent, AgentExecutor
    from langchain_core.prompts import ChatPromptTemplate

    # 初始化工具集
    toolkit = DatabaseToolkit()
    tools = toolkit.get_tools()

    # 创建 Agent
    llm = ChatOpenAI(
        model="deepseek-chat",
        base_url="https://api.deepseek.com/v1",
        api_key=os.getenv("DEEPSEEK_API_KEY"),
        temperature=0,
    )

    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个智能助手,可以使用工具来查询信息。"),
        ("human", "{input}"),
        ("placeholder", "{agent_scratchpad}"),
    ])

    agent = create_tool_calling_agent(llm, tools, prompt)
    agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

    # 测试
    result = agent_executor.invoke({"input": "帮我记住:张三是技术部的工程师"})
    print(f"\n结果: {result['output']}")

7.2 Agent 数据库工具架构

知识/文档
用户信息
保存记忆
用户请求
Agent
需要什么类型的数据?
search_knowledge
query_user_info
save_memory
向量数据库

ChromaDB
关系数据库

MySQL
返回结果
生成回复


8. 小结与下一步

8.1 本篇回顾

知识点 掌握程度
LangChain 记忆组件(ChatMessageHistory, RunnableWithMessageHistory)
LangChain + ChromaDB 向量数据库集成
LangChain + Milvus 企业级向量数据库集成
LangChain + Neo4j 图数据库集成
LangChain SQLDatabase MySQL 集成
使用 @tool 装饰器封装数据库工具
为 Agent 集成数据库工具

8.2 数据库选型速查

需求 推荐 理由
快速原型 / 学习 ChromaDB 零配置,pip install 即用
生产级大规模向量 Milvus 10亿+向量,GPU 加速
实体关系查询 Neo4j 图遍历,毫秒级
业务数据存储 MySQL + SQLAlchemy 成熟稳定,ORM 方便
全都要 三者配合使用 各司其职

8.3 LangChain 数据库组件总览

LangChain 数据库组件
VectorStore
Graph
SQLDatabase
Memory
Chroma
Milvus
FAISS
Neo4jGraph
NebulaGraph
MySQL
PostgreSQL
SQLite
ChatMessageHistory
RunnableWithMessageHistory

8.4 下一篇预告

第四篇:RAG 检索增强生成 将深入讲解:

  • LangChain RAG 完整流程(文档加载→分块→嵌入→检索→生成)
  • LangChain Document Loaders 和 Text Splitters
  • 使用 LangChain 构建 RAG Chain
  • 混合搜索(向量 + BM25)
  • 重排序(Reranking)
  • 生产级 RAG 管道完整实现
相关推荐
kcuwu.3 小时前
CNN与RNN入门技术博客
人工智能·rnn·cnn
2601_956414143 小时前
科捷智能仓储解决方案 ,助力两轮车行业高效运转
大数据·人工智能
@杰克成3 小时前
Java学习28
java·python·学习
hans汉斯3 小时前
【人工智能与机器人研究】基于改进YOLOv11的野外中草药目标检测
人工智能·yolo·目标检测·目标跟踪·机器人
mask哥3 小时前
codex安装并配置第三方大模型api方法详解
人工智能·ai编程·codex·vibecoding
Black蜡笔小新3 小时前
企业AI算力工作站DLTM深度学习推理工作站AI智检重构制造业质量管控新模式
人工智能·深度学习·重构
中国胖子风清扬3 小时前
PageIndex:用推理替代向量的下一代 RAG 架构
java·spring boot·python·spring·ai·embedding·rag
optimistic_chen3 小时前
【AI Agent 全栈开发】RAG(检索增强生成)
java·linux·运维·人工智能·ai编程·rag
我的xiaodoujiao3 小时前
API 接口自动化测试详细图文教程学习系列19--添加封装其他的方法
开发语言·python·学习·测试工具·pytest