Langchain+Neo4j+Agent 的结合案例-电商销售

目录

项目简介

一、构建知识图谱:

[二、 Cypher工具的开发](#二、 Cypher工具的开发)

1、链接数据库:

[2、Langchain 与 Neo4j 结合测试:](#2、Langchain 与 Neo4j 结合测试:)

三、tool

[1. email_tool](#1. email_tool)

[2. login_tool](#2. login_tool)

[3. neo4j_tool](#3. neo4j_tool)

四、service

[1. login_service](#1. login_service)

[2. rag_neo4j_service](#2. rag_neo4j_service)

[3. chat_service](#3. chat_service)

五、agent

[1. login_agent](#1. login_agent)

[2. chat_agent](#2. chat_agent)

六、view

[1. login_view](#1. login_view)

[2. chat_neo4j_view](#2. chat_neo4j_view)

七、前端

[1. login:](#1. login:)

[2. chat:](#2. chat:)

八、项目展示


项目简介

苯人这次的项目如标题所示,是将 Langchain+Neo4j+Agent 结合的案例,关于电商销售的。

随着电商行业的不断发展,平台积累了庞大的用户、商品与交易数据。如何从这些复杂数据中快速挖掘有价值的信息,满足用户个性化的消费需求,已经成为一个重要课题。传统的数据库查询方式操作复杂,用户往往需要专业知识才能获取所需信息,缺乏灵活性与智能化。本项目基于 LangChain 框架与 Neo4j 知识图谱,结合大语言模型的自然语言处理能力,构建了一个智能电商销售系统。系统不仅能够支持用户的自动注册与登录(包含智能体自动生成账号、验证码邮件发送与后台数据库写入),还实现了基于知识图谱的数据问答,用户只需提出自然语言问题,即可获得关于电商销售的个性化回答。例如,用户可以直接询问"用户1的消费偏好品牌",系统会自动转换为图数据库查询并返回结果。通过这一项目,能够探索知识图谱与大模型在电商场景中的结合应用,提升数据交互的智能化与便捷性。

项目架构主要分为三层:数据层,模型层,应用层。

数据层:用 Neo4j 构建电商知识图谱,存储用户、订单、商品等;

模型层:基于 LangChain 框架,接入大语言模型(LLaMA 等),通过 Agent 调用 Neo4j 的查询工具,实现基于知识图谱的 RAG,支持个性化推荐和历史问题查询;

应用层:用户通过前端交互,像注册、登录等流程也交由 Agent 驱动,Agent 会自动选择和调用合适的工具去完成多步骤任务。

整体来说, Agent 起到大脑的作用,Neo4j 是知识库,Langchain 负责把它们结合起来,应用层把这些能力对外提供服务。

一句话总结流程:用户从前端发送请求 -> view(视图层) -> agent(智能体层) -> service(服务层) -> tool(工具层) -> 数据库 -> 一路返回 LLM 生成的解释性答案给用户

这里的每一个层其实就是文件夹,但其实写代码的顺序不是按照上面的流程来的,下面分步说明:

一、构建知识图谱:

在创建数据库的时候要运行这段代码:

python 复制代码
:use system;
CREATE DATABASE ecommerce   

ecommerce 就是数据库名,然后在这个数据库里创建:

然后生成的数据长这样,以 User 和 PLACED 关系为例:

我是让AI生成了100个用户,15个品类,30个品牌,500个商品,1500个订单,适用于我们这个案例,接下来就是写查询语句看节点关系这些是否正确创建了,比如测几个常用查询:

用户买得最多的商品前三:

复制代码
MATCH (u:User {userId:"U1"})-[:PLACED]->(:Order)-[c:CONTAINS]->(p:Product)
RETURN p.name AS Product, sum(c.quantity) AS TotalQuantity
ORDER BY TotalQuantity DESC
LIMIT 3;

运行结果:

用户最近一次下单的内容:

复制代码
MATCH (u:User {userId:"U1"})-[:PLACED]->(o:Order)-[c:CONTAINS]->(p:Product)
WITH u, o, p ORDER BY o.createTime DESC
WITH u, collect(DISTINCT {order:o.orderId, product:p.name})[0] AS LastOrder
RETURN LastOrder;

用户的品牌偏好:

复制代码
MATCH (u:User {userId:"U1"})-[:PLACED]->(:Order)-[c:CONTAINS]->(p:Product)-[:PRODUCED_BY]->(b:Brand)
RETURN b.name AS Brand, sum(c.quantity) AS TotalBought
ORDER BY TotalBought DESC;

销量最好的商品前十:

复制代码
MATCH (p:Product)<-[c:CONTAINS]-(:Order)
OPTIONAL MATCH (p)-[:BELONGS_TO]->(cat:Category)
OPTIONAL MATCH (p)-[:PRODUCED_BY]->(b:Brand)
RETURN p.name AS Product,
       cat.name AS Category,
       b.name AS Brand,
       sum(c.quantity) AS TotalSold
ORDER BY TotalSold DESC
LIMIT 10;

品牌对比销量:

复制代码
MATCH (b:Brand)<-[:PRODUCED_BY]-(p:Product)<-[c:CONTAINS]-(:Order)
WHERE b.name IN ["苹果","华为"]
OPTIONAL MATCH (p)-[:BELONGS_TO]->(cat:Category)
RETURN b.name AS Brand, p.name AS Product, cat.name AS Category,
       sum(c.quantity) AS TotalSold
ORDER BY Brand, TotalSold DESC;

以上都有结果就说明数据库基本是没问题了,下一步就是 Cypher工具的开发,包括编写一个可靠的函数,接收自然语言问题,利用LLM(或经过微调的模型)生成Cypher查询,也就是让模型自己写上面那种查询语句,代码如下:

二、 Cypher工具的开发

因为要测试通过了才可以正式写入后端,所以先测试:

1、链接数据库:

python 复制代码
# 导入操作使用到的包
from langchain_neo4j import Neo4jGraph

url ="bolt://localhost:7687"
username="neo4j"
password="12345678"
data_base = "ecommerce"
graph = Neo4jGraph(url=url, username=username, password=password, database=data_base)
print("链接成功!")

接下来就是将 Langchain 与 知识图谱结合起来:

2、Langchain 与 Neo4j 结合测试:

python 复制代码
import os
from dotenv import load_dotenv
from langchain.chains import GraphCypherQAChain
from langchain_community.graphs import Neo4jGraph
from langchain_community.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from my_chat.my_chat_model import ChatModel

import warnings
from langchain_core._api import LangChainDeprecationWarning
warnings.filterwarnings("ignore", category=LangChainDeprecationWarning)

#langchain结合neo4j完成问答案例
def test1():
    load_dotenv()
    #获取在线模型
    chat = ChatModel()
    llm = chat.get_online_model()
    prompt = ChatPromptTemplate.from_messages([
        ("system", """你是一个超级专业且很很会看注意事项的Neo4j Cypher查询生成器,现在有一个包含用户购买记录以及品类品牌信息的知识图谱,
            根据用户的问题生成合适的Cypher查询。生成查询时请严格遵循以下规则:
        
            数据库模式:
            - User节点属性: name, userId (例如: "U1", "U2")
            - Product节点属性: productId (例如: "P1"), price, name
            - Category节点属性: name, categoryId (例如: "C1")
            - Brand节点属性: brandId (例如: "B1"), name
            - Order节点属性: totalAmount, createTime, orderId (例如: "O1")
        
            关系及方向:
            - (u:User)-[:PLACED]->(o:Order)
            - (o:Order)-[:CONTAINS]->(p:Product)
            - (p:Product)-[:BELONGS_TO]->(c:Category)
            - (p:Product)-[:PRODUCED_BY]->(b:Brand)
        
            注意事项:
            1. 所有 ID 属性都是字符串形式,带前缀 (U, P, C, B, O)。
            2. [:CONTAINS] 关系上有属性 quantity ,用 c.quantity 引用,引用前先用 [c:CONTAINS] 给关系绑定变量,不能直接引用未定义的变量比如c.quantity 中的 c
               - 不要在 MATCH 中直接引用未定义变量
               - c.quantity 表示用户在订单中的购买数量,不是库存量!!!
               - 当统计品牌/品类销量或消费时,先用 [c:CONTAINS] 给关系绑定变量才能引用 c.quantity
               - 当用户问"买得最多/销量最高/最常买"时,必须使用 sum(c.quantity)。
            3. 关系方向必须严格遵守上面定义,绝不能写反。
            4. 每次返回商品信息时,同时显示品类和品牌,避免只显示"商品1""商品2"。
            5. 当返回商品的品类或品牌时,先用 OPTIONAL MATCH 绑定节点,再用节点属性 .name 返回。
            """),
        ("human", "{query}"),
    ])

    #链接数据库
    graph = Neo4jGraph(
        url=os.getenv("NEO4J_URI"),
        username=os.getenv("NEO4J_USERNAME"),
        password=os.getenv("NEO4J_PASSWORD"),
        database="ecommerce" #要换成自己的数据库名
    )
    #创建一个链
    chain = GraphCypherQAChain.from_llm(
        llm=llm,
        graph=graph,
        allow_dangerous_requests=True,
        cypher_prompt=prompt,
        top_k=10,
        verbose=True, #日志用于调试使用 可以省略
    )
    rs = chain.invoke({"query": "用户2的品牌偏好"})
    print(rs)

if __name__ == '__main__':
    test1()

具体流程就是获取在线大模型,生成提示模版后创建链,然后对问题进行响应,运行结果如下:

最需要注意的就是提示词模版这一块了,自己写了才知道为什么会有专门的提示词工程师这个职位,这个提示词写得有一点不对或者不全面就会报错,而且就算写了有时候也会报同样的错,所以要不断调试才行。调试结果都没问题后就可以写工具了:

三、tool

这次一共有三个工具,分别是 email_tool、login_tool、neo4j_tool,下面分别按先后顺序介绍它们:

1. email_tool

从发送验证码邮件的工具开始,也就是用户登录系统的第一种方式:邮箱验证码登录,具体代码如下:

python 复制代码
import requests  # 用于发送HTTP请求到心知天气API
from dotenv import load_dotenv  # 用于加载环境变量文件(.env)
import os  # 用于访问操作系统环境变量
from langchain.tools import BaseTool  # LangChain的工具基类
from pydantic import BaseModel, Field, ConfigDict  # 数据验证和设置管理
from typing import Optional, Type, Any  # 类型注解
import os
from email.mime.text import MIMEText
import smtplib
from dotenv import load_dotenv

# 1先定义数据模型
class Email(BaseModel):
    #描述要准确
    to_email: str = Field(..., description="收件人的邮箱")
    subject: str = Field(..., description="邮箱的标题")
    content: str = Field(..., description="邮箱的内容")

#2 定义配置管理类 表示模型字段的配置 智能体工具类
class EmailTool(BaseTool):
    #arbitrary_types_allowed=True:允许数据类型采用Python的任意数据类型
    model_config = ConfigDict(arbitrary_types_allowed=True)

    #定义一个初始化方法
    def __init__(self, **kwargs: Any):
        super().__init__(
            name="get_email_tool",
            description = "用于发送邮件信息的,输入的参数应该是收件人的邮箱、邮件标题和邮件内容",
            **kwargs
        )
    #定义智能体的工具
    args_schema: Type[BaseTool] = Email

    # 定义执行方法
    def _run(self, to_email: str, subject: str, content: str) -> str:
        load_dotenv()
        # 创建邮件对象 content是邮件内容
        msg = MIMEText(content)
        # 收件人邮箱
        msg['To'] = to_email
        ##发件人邮箱
        msg['From'] = os.getenv("email_user")
        # 邮件主题
        msg['Subject'] = subject
        # 捕获异常
        try:
            # 创建SMTP对象
            smtp = smtplib.SMTP_SSL(host=os.getenv("email_host"), port=465)
            # 登录邮箱
            smtp.login(os.getenv("email_user"), os.getenv("email_password"))
            # 发送邮件 参数分别是发件人,收件人,邮件内容类型转换
            smtp.sendmail(os.getenv("email_user"), to_email, msg.as_string())
            # print("邮件发送成功!")
            #一定要加返回值
            return "邮件发送成功!"
        except Exception as e:
            print(e)
            print("邮件发送失败")

首先先描述输入参数,第一个Email类就是告诉智能体发送邮件时需要哪些参数,每个参数什么意思;第二个EmailTool 就是继承了BaseTool 的工具类;第三个 _run() 就是核心方法了,这里面就要写具体发送邮件的流程,可以先单独写一个测试文件看是否能成功运行,然后再粘过来,注意这里的环境变量要配好。下一步就是登录系统的工具,代码流程都跟这个雷同:

2. login_tool

这个工具就是用于校验用户的第二种登录方式:用户名加密码登录,实现流程是用户输入用户名和密码后,工具会去数据库表里查询是否存在,背后靠的是 Langchain 的SOL查询链,也就是用大模型生成 SQL 语句然后执行数据库查询,代码如下:

python 复制代码
from langchain.tools import BaseTool
from pydantic import BaseModel, Field, ConfigDict
from typing import Any, Type, Optional
from dotenv import load_dotenv
import os
from model.my_chat_model import ChatModel
from langchain.chains import create_sql_query_chain
from langchain_community.utilities import SQLDatabase

#定义输入参数的数据模型类
class LoginInput(BaseModel):
    name: str = Field(..., description="用户名")
    password: str = Field(..., description="密码")


#定义工具类
class LoginTool(BaseTool):
    #定义模型是否允许输入参数
    model_config = ConfigDict(arbitrary_types_allowed=Type)
    #初始化
    def __init__(self, **kwargs):
        super().__init__(
            name = "get_login_tool",
            description = "主要用于完成系统的登录功能,必须输入用户名和密码,且都与数据库里的匹配才行",
            **kwargs
        )
    #定义工具参数
    args_schema : Type[BaseModel] = LoginInput

    def _run(self, name: str, password: str):
        # 获取大模型
        chat = ChatModel()
        llm = chat.get_online_model()
        # 创建数据库链接
        db = SQLDatabase.from_uri(
            "mysql+pymysql://root:root@localhost:3306/0908",
            include_tables=["user_info"],
        )
        # 创建 SQL查询链
        chain = create_sql_query_chain(llm, db)
        # 提问(让大模型生成SQL)
        question = f"请根据用户名是{name}和密码是{password}来查询信息"
        sql = chain.invoke({"question": question})
        print(sql)

        # 格式化SQL语句
        if "```sql" in sql:
            sql = sql.split("```sql")[1].split("```")[0]
        # print(sql)
        
        #执行查询并返回结果
        rs = db.run(sql)
        print(rs)
        return rs

同样,_run() 方法可以先用测试文件测试:

查询成功后就直接粘过去就行,到这里其实就可以写智能体了,因为已经有了验证码+登录的完整流程,但是因为是按文件夹顺序来的,所以还是先把tool写完吧,最后一个是 neo4j_tool:

3. neo4j_tool

这个工具是知识图谱的专用工具,实现流程就是 用户提问-> 工具生成Cypher查询 -> 执行Neo4j数据库 -> 返回查询结果,代码如下:

python 复制代码
from langchain.tools import BaseTool
from pydantic import BaseModel, Field, ConfigDict
from typing import Any, Type, Optional
from dotenv import load_dotenv
import os
from model.my_chat_model import ChatModel
from langchain_neo4j import GraphCypherQAChain, Neo4jGraph
from langchain_core.prompts import ChatPromptTemplate

#定义输入参数的数据模型类
class Neo4jInput(BaseModel):
    question: str = Field(..., description="问题")

#定义工具类
class Neo4jTool(BaseTool):
    #定义模型是否允许输入参数
    model_config = ConfigDict(arbitrary_types_allowed=Type)
    #初始化
    def __init__(self, **kwargs):
        super().__init__(
            name = "get_neo4j_tool",
            description = "用于查询电商销售知识图谱的数据,主要关于用户/商品/品牌/品类,必须输入问题",
            **kwargs
        )
    #定义工具参数
    args_schema : Type[BaseModel] = Neo4jInput
    #定义工具方法
    def _run(self, question: str):
        #加载环境变量
        load_dotenv()
        #获取模型
        chat = ChatModel()
        llm = chat.get_online_model()
        # 连接图形数据库
        graph = Neo4jGraph(
            url=os.getenv("NEO4J_URI"),
            username=os.getenv("NEO4J_USERNAME"),
            password=os.getenv("NEO4J_PASSWORD"),
            database="ecommerce" #换成自己的数据库名
        )
        #创建提示模型
        # 这里的system描述很重要
        prompt = ChatPromptTemplate.from_messages([
            ("system", """你是一个非常专业的Neo4j Cypher查询生成器,现在有一个包含用户的订单历史记录数据的知识图谱,
                    根据用户的问题生成合适的Cypher查询。生成查询时请严格遵循以下规则:

                    数据库模式:
                    - User节点属性: name, userId (例如: "U1", "U2")
                    - Product节点属性: productId (例如: "P1"), price, name
                    - Category节点属性: name, categoryId (例如: "C1")
                    - Brand节点属性: brandId (例如: "B1"), name
                    - Order节点属性: totalAmount, createTime, orderId (例如: "O1")

                    关系及方向:
                    - (u:User)-[:PLACED]->(o:Order)
                    - (o:Order)-[:CONTAINS]->(p:Product)
                    - (p:Product)-[:BELONGS_TO]->(c:Category)
                    - (p:Product)-[:PRODUCED_BY]->(b:Brand)

                    注意事项:
                    1. 所有 ID 属性都是字符串形式,带前缀 (U, P, C, B, O)。
                    2. [:CONTAINS] 关系上有属性 quantity ,用 c.quantity 引用,引用前先用 [c:CONTAINS] 给关系绑定变量,不能直接引用未定义的变量比如c.quantity 中的 c
                       - 不要在 MATCH 中直接引用未定义变量
                       - c.quantity 表示用户在订单中的购买数量,不是库存量!!!
                       - 当统计品牌/品类销量或消费时,先用 [c:CONTAINS] 给关系绑定变量才能引用 c.quantity
                       - 当用户问"买得最多/销量最高/最常买"时,必须使用 sum(c.quantity)
                    3. 关系方向必须严格遵守上面定义,绝不能写反。
                    4. [:CONTAINS] 关系 和 Category节点的变量不能都是相同的c
                    5. 每次返回商品信息时,同时显示品类和品牌,避免只显示"商品1""商品2"。
                    6. 当返回商品的品类或品牌时,先用 OPTIONAL MATCH 绑定节点,再用节点属性 .name 返回。
                    7. OPTIONAL MATCH 语句必须放在 RETURN 之前,不能嵌套在 RETURN 中
                    """),
            ("human", "{query}"),
        ])
        # 创建链
        chain = GraphCypherQAChain.from_llm(
            llm=llm,
            graph=graph,
            allow_dangerous_requests=True,
            cypher_prompt=prompt,
            top_k=10,
            verbose=True,  # 日志用于调试使用 可以省略
        )
        #提问
        rs = chain.invoke({"query": question})
        #返回答案
        return rs["result"]

前面都差不多,后面的 _run() 方法就是一开始 Langchain 与 Neo4j 结合测试的代码,直接粘过来就是。

现在 tool 层写完了,每一个 tool 就像是一把专用的螺丝刀,用于完成特定的小任务,例如发送邮件登录、验证用户登录、查询知识图谱,它们轻量灵活,而接下来的service 层则是将这些基础工具进行整合和封装,形成一整套可复用的业务处理流程,就像一个已经配备好螺丝刀、电钻、手电筒的工具箱,方便后面的 agent 直接调用,下面开始:

四、service

1. login_service

封装所有的登录业务细节,不管是用户密码登录还是邮箱发送验证码登录,代码如下:

python 复制代码
# service/login_service.py
from tool.login_tool import LoginTool
from tool.email_tool import EmailTool
from model.my_chat_model import ChatModel
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

class LoginService:
    def __init__(self):
        self.chat = ChatModel()
        self.llm = self.chat.get_online_model()
        self.tools = [LoginTool(), EmailTool()]

        self.prompt = ChatPromptTemplate.from_messages([
            ("system", """
               你是一个智能的查询助手,你可以使用以下工具:
                    1. get_login_tool: 完成系统登录,用户名和密码必须正确
                    2. get_email_tool: 发送邮件信息
               根据用户需求智能选择工具:
                    - 用户名密码登录 → 执行 get_login_tool
                    - 发送验证码邮件 → 执行 get_email_tool
            """),
            ("human", "{input}"),
            ("placeholder", "{agent_scratchpad}")  # 工具调用
        ])

    def process_request(self, question: str):
        agent = create_tool_calling_agent(self.llm, self.tools, self.prompt)
        agent_executor = AgentExecutor(
            agent=agent,
            tools=self.tools,
            verbose=True
        )
        rs = agent_executor.invoke({"input": question})
        return rs["output"]

解释一下,这个代码文件名叫 login_service,封装了一个服务类叫 LoginService,它对外提供的方法是 process_request,调用这个接口就可以直接登录系统

2. rag_neo4j_service

用于封装 Langchain 与 Neo4j 的连接与对话链创建过程,后面的的 chat_agent 只需调用这个 service,就能直接获得一个可用的知识图谱问答链,而不必关心底层的配置与初始化细节,具体代码如下:

python 复制代码
import os
from dotenv import load_dotenv
from langchain.chains import GraphCypherQAChain
from langchain_community.graphs import Neo4jGraph
from langchain_community.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from model.my_chat_model import ChatModel

import warnings
from langchain_core._api import LangChainDeprecationWarning
warnings.filterwarnings("ignore", category=LangChainDeprecationWarning)

class Ragneo4jservice:
    #数据初始化
    def __init__(self):
        #加载环境变量
        load_dotenv()
        #初始化模型
        self.chat=ChatModel()
        self.llm = self.chat.get_online_model()
        #初始化图形数据库
        self.graph = Neo4jGraph(
            url=os.getenv("NEO4J_URI"),
            username=os.getenv("NEO4J_USERNAME"),
            password=os.getenv("NEO4J_PASSWORD"),
            database="ecommerce" #要换成自己的数据库名
        )

    #创建一个对话链
    def create_chain(self):
        # 这里的system描述很重要
        prompt = ChatPromptTemplate.from_messages([
            ("system", """你是一个专业的Neo4j Cypher查询生成器,现在有一个关于用户的历史购买记录的知识图谱,
                    根据用户的问题生成合适的Cypher查询。生成查询时请严格遵循以下规则:

                    数据库模式:
                    - User节点属性: name, userId (例如: "U1", "U2")
                    - Product节点属性: productId (例如: "P1"), price, name
                    - Category节点属性: name, categoryId (例如: "C1")
                    - Brand节点属性: brandId (例如: "B1"), name
                    - Order节点属性: totalAmount, createTime, orderId (例如: "O1")

                    关系及方向:
                    - (u:User)-[:PLACED]->(o:Order)
                    - (o:Order)-[:CONTAINS]->(p:Product)
                    - (p:Product)-[:BELONGS_TO]->(c:Category)
                    - (p:Product)-[:PRODUCED_BY]->(b:Brand)

                    注意事项:
                    1. 所有 ID 属性都是字符串形式,带前缀 (U, P, C, B, O)。
                    2. [:CONTAINS] 关系上有属性 quantity表示购买数量,用 c.quantity 引用,
                       - 当统计品牌/品类销量或消费时,先用 [c:CONTAINS] 给关系绑定变量。
                       - 当用户问"买得最多/销量最高/最常买"时,必须使用 sum(c.quantity)。
                       - 不要在 MATCH 中直接引用未定义变量
                    3. 关系方向必须严格遵守上面定义,绝不能写反。
                    4. 每次返回商品信息时,同时显示品类和品牌,避免只显示"商品1""商品2"。
                    5. 当返回商品的品类或品牌时,先用 OPTIONAL MATCH 绑定节点,再用节点属性 .name 返回。
                    """),
            ("human", "{query}"),
        ])
        #创建LangChain 的 Neo4j 问答链
        chain = GraphCypherQAChain.from_llm(
            llm=self.llm,
            graph=self.graph,
            allow_dangerous_requests=True,
            cypher_prompt=prompt,
            top_k=10,
            verbose=True,  # 日志用于调试使用 可以省略
        )
        #返回链对象
        return chain

调用 Ragneo4jservice 类的 create_chain 接口就能得到一个完整的 Langchain问答链,也就是只需要:

python 复制代码
service = Ragneo4jservice()
chain = service.create_chain()
answer = chain.invoke({"query": "用户U1最喜欢的品牌是什么?"})

3. chat_service

这里主要是实现大模型既能基于知识图谱做专业回答,又能进行普通的聊天问答,代码如下:

python 复制代码
# service/chat_service.py
import os
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from model.my_chat_model import ChatModel
from tool.neo4j_tool import Neo4jTool
from tool.document_tool import DocumentTool  # 可选


class ChatService:
    def __init__(self):
        # 初始化大模型
        self.chat = ChatModel()
        self.llm = self.chat.get_online_model()

        # 初始化工具(Neo4jTool 内部会用 Ragneo4jservice)
        self.tools = [Neo4jTool(), DocumentTool()]

        # 提示模版
        self.prompt = ChatPromptTemplate.from_messages([
            ("system", """
            你是一个非常智能的助手,可以回答普通问题,可以陪用户聊天,也可以调用以下工具:
              1. get_neo4j_tool: 查询知识图谱中的消费记录、商品、品牌、品类等相关问题。
              2. get_document_tool: 查询文档内容。

            使用规则:
            - 如果问题是图数据库相关的,必须调用 get_neo4j_tool。
            - 如果是文档类问题,调用 get_document_tool。
            - 如果是普通聊天或代码问题,不调用工具,直接回答。
            """),
            ("human", "{input}"),
            ("placeholder", "{agent_scratchpad}")
        ])

        # 构建智能体
        self.agent = create_tool_calling_agent(self.llm, self.tools, self.prompt)
        self.agent_executor = AgentExecutor(
            agent=self.agent,
            tools=self.tools,
            verbose=True
        )

    """智能体问答接口"""
    def answer_question(self, question: str):
        rs = self.agent_executor.invoke({"input": question})
        return rs["output"]

虽说不一定必须要有 service 层,但是它会让agent写起来更干净更简单,如果直接让 agent 去写所有东西,比如大模型初始化、Neo4j 连接、prompt 构建,那 agent 的代码会变得很臃肿,而且逻辑耦合度会非常高,一旦 Neo4j 地址、数据库模式或 prompt 规则变化,那要去每个 agent 里改,很麻烦,所以为了代码结构清晰、维护方便、方便扩展,service 层几乎是必须的设计,尤其是项目复杂度提升的时候。

那么接下来就可以开始写 agent了:

五、agent

agent 其实相当于经理,决定什么时候用工具,什么时候直接回答

这里我们设计了两种 agent ,一个是发送邮件,一个是用户密码登录,由于我们前面封装了service 类,所以这里的代码就很简短,直接跟着运行结果一起看:

1. login_agent

python 复制代码
# agent/login_agent.py
from service.login_service import LoginService

def create_login(question):
    service = LoginService()
    return service.process_request(question)

if __name__ == '__main__':
    que = "请登录系统,用户名:老板, 密码:123456"
    print(create_login(que))

运行结果:

2. chat_agent

python 复制代码
# agent/chat_agent.py
from service.chat_service import ChatService

def create_agent(question):
    service = ChatService()
    return service.answer_question(question)

if __name__ == '__main__':
    print(create_agent("用户1购买得最多的商品是什么"))   # 知识图谱问答
    # print(create_agent("请写一下Python的hello world程序"))  # 普通聊天

运行结果:

这表明大模型可以基于不同的问题选择是否调用工具,如果未来的提示模版需要改动只需要改 service 里的代码就好,agent 只专注于调用业务服务,维护更简单。

到此,我们已经把 agent 层 和 service 层 区分开了,agent 现在只负责调用,不再堆满大模型、工具、prompt 的细节,而 service 封装了所有业务逻辑(模型初始化、工具加载、提示词配置、执行调用),接下来就是 view层了,用来接收用户的输入和展示 agent 的输出,也就是构建前后端交互的接口,这里我们用 Django 来写。

六、view

view层的作用流程大概是这样:

接收用户的HTTP请求(前端传来的参数)-> 调用agent(通过service) -> 把结果包装成HTTP响应返回前端

所以说,view 就像一个前台,将用户的需求告诉经理(agent),经理选择是否需要调用专业人员(是否使用工具),最后把经理处理的结果用用户能听懂的话(json格式数据)解释给用户,具体代码如下:

1. login_view

python 复制代码
import random

from django.shortcuts import HttpResponse
from agent.login_agent import create_login
import json

#账号登录接口
def login(request):
    if request.method == 'GET':
        name = request.GET.get("name")
        password = request.GET.get("password")
        #创建一个登录的智能体
        question = f"请登录系统,用户名:{name}, 密码是:{password}"
        answer = create_login(question)
        if "成功" in answer:
            data = {
                "code": 200,
                "msg": "登录成功"
            }
            return HttpResponse(json.dumps(data))
        else:
            data = {
                "code": 500,
                "msg": "登录失败"
            }
            return HttpResponse(json.dumps(data))

#发送邮箱验证码接口
def send_email(request):
    if request.method == 'GET':
        email = request.GET.get("email")
        #随机生成验证码
        code = str(random.randint(1000, 10000))
        question = f"给用户的邮箱{email}发送验证码邮件{code},邮件标题是知识图谱后台系统验证码登录"
        answer = create_login(question)
        if "成功" in answer:
            data = {
                "code": 200,
                "msg": "发送成功"
            }
            rs = HttpResponse(json.dumps(data))
            #设置cookie max_age表示生效时间,单位为秒
            rs.set_cookie("code", code, max_age=300)
            return rs
        else:
            data = {
                "code": 500,
                "msg": "发送失败"
            }
            return HttpResponse(json.dumps(data))

#验证码登录
def code_login(request):
    if request.method == 'GET':
        code = request.GET.get("code")
        #获取cookie
        cookie_code = request.COOKIES.get("code")
        if not cookie_code: #验证码不存在或失效
            data = {
                "code": 500,
                "msg": "验证码失效,请重新发送"
            }

            return HttpResponse(json.dumps(data))
        elif code == cookie_code:
            data = {
                "code": 200,
                "msg": "登录成功!"
            }
            return HttpResponse(json.dumps(data))

这里面定义的三个函数就分别是三个接口,login 用于取出 name 和 password,丢给 agent 去跑后返回"登录成功"或"登录失败";send_email 用于取出 email并生成随机验证码,然后调用 agent 让它去发邮件,同时把验证码存在 cookie,方便后续校验;code_login 用于将前端传来的验证码与cookie 里的验证码对比,同样返回"登录成功"或"登录失败"。

所以总结一句,**view 层就是负责 "请求 → 调 agent → 返回响应" 的那一层,是真正的前后端交互接口,**它自己不写复杂逻辑,逻辑都已经在 service/agent/tool 里拆好了。

2. chat_neo4j_view

是同样的逻辑:

python 复制代码
import json
from django.shortcuts import HttpResponse #响应库
from agent.chat_agent_simple import create_agent

#聊天
def chat(request):
    if request.method == "GET":
        #获取用户发送的问题
        question = request.GET.get("question")
        print(f"question={question}")
        try:
            #创建智能体
            answer = create_agent(question)

            # 设置响应数据格式
            data = {
                "code": 200,
                "msg": "success",
                "data": answer
            }
            return HttpResponse(json.dumps(data))

        except Exception as e:
            print(e)
            data = {
                "code": 200,
                "msg": "服务器错误",
                "data": "我不知道答案捏"
            }
            return HttpResponse(json.dumps(data))

那现在我们有了 chat、login、sendEmail、codeLogin 这些接口,接下来就可以开始前端了

七、前端

前端的话,我们主要做了几个页面,有登录 login、聊天问答 chat 、索引 index(不是重点),当然因为前端本身对我来说就不是重点,所以这里直接贴主要代码了:

1. login:

html 复制代码
<template>
  <div class="login-one">

    <el-row>
      <el-col :span="8">&nbsp;</el-col>
      <el-col :span="8">
        <br><br><br><br><br><br><br><br><br>
        <!--        <h1 align="center" style=" font-family: Helvetica;">零售智控管理平台</h1>-->
        <h1 align="center"
            style="font-family: 'Roboto', sans-serif;
           font-weight: 700;
           font-size: 42px;
           color: #34495e;
           text-shadow: none;">
          零售智控管理平台
        </h1>

        <el-tabs type="border-card">
          <!--用户注册-->
          <el-tab-pane label="用户注册">
            <el-form label-width="80px" style="background-color: white;border-radius: 3%;padding-top: 5px">
              <el-form-item label="用户名">
                <el-col :span="20">
                  <el-input v-model="registerName"></el-input>
                </el-col>
              </el-form-item>

              <el-form-item label="密码">
                <el-col :span="20">
                  <el-input v-model="registerPassword" show-password></el-input>
                </el-col>
              </el-form-item>

              <el-form-item>
                <el-col :span="6">&nbsp;</el-col>
                <el-col :span="10">
                  <el-button type="primary" icon="el-icon-edit" size="mini" @click="register">注册</el-button>
                </el-col>
                <el-col :span="8">&nbsp;</el-col>
              </el-form-item>
            </el-form>
          </el-tab-pane>

          <!-- 用户登录 -->
          <el-tab-pane label="用户登录">
            <el-form label-width="80px" style="background-color: white;border-radius: 3%;padding-top: 5px">

              <el-form-item label="用户名">
                <el-col :span="20">
                  <el-input v-model="name"></el-input>
                </el-col>
              </el-form-item>

              <el-form-item label="密码">
                <el-col :span="20">
                  <el-input v-model="password" show-password></el-input>
                </el-col>
              </el-form-item>

              <el-form-item>
                <el-col :span="6">&nbsp;</el-col>
                <el-col :span="10">
                  <el-button type="success" icon="el-icon-s-custom" size="mini" @click="login">登录</el-button>
                </el-col>
                <el-col :span="8">&nbsp;</el-col>
              </el-form-item>
            </el-form>
          </el-tab-pane>

          <!-- 邮箱登录 -->
          <el-tab-pane label="邮箱登录">
            <el-form label-width="80px" style="background-color: white;border-radius: 3%;padding-top: 5px">


              <el-form-item label="邮箱">
                <el-col :span="20">
                  <el-input v-model="email"></el-input>
                </el-col>
              </el-form-item>

              <el-form-item label="验证码">
                <el-col :span="20">
                  <el-input v-model="code" show-password></el-input>
                </el-col>
              </el-form-item>

              <el-form-item>
                <el-col :span="6">&nbsp;</el-col>
                <el-col :span="10">
                  <el-button type="success" icon="el-icon-s-custom" size="mini" @click="send_code">发送验证码
                  </el-button>
                  <el-button type="success" icon="el-icon-s-custom" size="mini" @click="code_login">登录</el-button>
                </el-col>
                <el-col :span="8">&nbsp;</el-col>
              </el-form-item>
            </el-form>

          </el-tab-pane>


        </el-tabs>

      </el-col>
      <el-col :span="8">&nbsp;</el-col>
    </el-row>
  </div>
</template>

<script>
export default {
  name: "Login",
  data() {
    return {
      name: "",
      password: "",
      email: "",
      code: "",
      registerName: "",
      registerPassword: ""

    }
  },
  methods: {
    login() {
      const self = this;
      this.$http.get("/api/login/",
        {params: {name: self.name, password: self.password}})
        .then(function (rs) {
          let code = 0;
          let msg = "";

          if (rs.data && typeof rs.data === "object" && "code" in rs.data && "msg" in rs.data) {
            // 正确登录返回对象
            code = rs.data.code;
            msg = rs.data.msg;
          } else if (typeof rs.data === "string") {
            // 错误登录返回字符串
            msg = rs.data;
            // 根据字符串关键字判断状态码
            if (msg.includes("成功")) {
              code = 200;
            } else {
              code = 500;
            }
          } else {
            msg = JSON.stringify(rs.data);
            code = 500;
          }

          if (code === 200) {
            self.$message.success(msg);
            self.$router.push("/index");
          } else {
            self.$message.error(msg);
          }
        })
        .catch(function (err) {
          console.log(err);  // 打印错误信息
          self.$message.error("请求失败:" + err);
        });
    },
    send_code() {
      const self = this;
      this.$http.get("/api/sendEmail/", {params: {"email": self.email}})
        .then(function (rs) {
          if (rs.data.code === 200) {
            self.$message(rs.data.msg);
            // //跳转到聊天页面
            // self.$router.push("/index");
          } else {
            self.$message(rs.data.msg);
          }
        })
    },
    code_login() {
      const self = this;
      this.$http.get("/api/codeLogin/", {params: {"code": self.code}})
        .then(function (rs) {
          if (rs.data.code === 200) {
            self.$message(rs.data.msg);
            //跳转到聊天页面
            self.$router.push("/index");
          } else {
            self.$message(rs.data.msg);
          }
        })

    },
    register() {
      const self = this;
      this.$http.get("/api/register/", {
        params: {
          name: self.registerName,
          password: self.registerPassword
        }
      })
        .then(function (rs) {
          // rs.data 是对象,不是字符串!
          let msg = rs.data.msg || "操作完成";  // 提取 msg 字段

          // 确保 msg 是字符串
          if (typeof msg !== 'string') {
            msg = String(msg);
          }

          // 现在 msg 是字符串,可以安全使用 includes
          if (msg.includes("成功")) {
            self.$message.success(msg);
            self.$router.push("/login");
          } else if (msg.includes("失败") || rs.data.code === 500) {
            self.$message.error(msg);
          } else {
            self.$message.info(msg);
          }
        })
        .catch(function (err) {
          self.$message.error("请求失败:" + err);
        });
    }


  }

}
</script>

<style scoped>
@import url('../../assets/css/login.css');


</style>

2. chat:

html 复制代码
<template>
  <div class="chat-container">
    <!-- 顶部导航栏 -->
    <el-header class="chat-header">
      <div class="header-content">
        <div class="logo">

          <span>DeepSeek Chat</span>
        </div>
        <div class="header-actions">
          <el-button type="success" icon="el-icon-refresh" @click="resetConversation">新对话</el-button>

        </div>
      </div>
    </el-header>

    <!-- 主聊天区域 -->
    <el-main class="chat-main">
      <div class="message-container" ref="messageContainer">
        <!-- 欢迎消息 -->
        <div class="welcome-message" v-if="messages.length === 0">
          <h2>欢迎使用 DeepSeek Chat</h2>
          <p>我是您的AI助手,可以回答各种问题、帮助创作和提供建议</p>
          <div class="quick-questions">
            <el-button
              v-for="(question, index) in quickQuestions"
              :key="index"
              round
              @click="sendQuickQuestion(question)"
            >
              {{ question }}
            </el-button>
          </div>
        </div>

        <!-- 消息列表 -->
        <div
          v-for="(message, index) in messages"
          :key="index"
          class="message-item"
          :class="{'user-message': message.role === 'user', 'ai-message': message.role === 'assistant'}"
        >
          <div class="message-avatar">
            <img
              v-if="message.role === 'user'"
              src="../../assets/images/user.jpeg"
              alt="User"
            >
            <img
              v-else
              src="../../assets/images/bot.jpeg"
              alt="AI"
            >
          </div>
          <div class="message-content">
            <div class="message-text" v-html="formatMessage(message.content)"></div>
            <div class="message-actions">
              <el-button
                v-if="message.role === 'assistant'"
                type="text"
                icon="el-icon-copy-document"
                size="mini"
                @click="copyToClipboard(message.content)"
              >
                复制
              </el-button>
              <el-button
                type="text"
                icon="el-icon-thumb"
                size="mini"
                @click="rateMessage(index, 'like')"
                :class="{active: message.rating === 'like'}"
              >
                {{ message.likes || 0 }}
              </el-button>
              <el-button
                type="text"
                icon="el-icon-thumb"
                size="mini"
                @click="rateMessage(index, 'dislike')"
                :class="{active: message.rating === 'dislike'}"
              >
                {{ message.dislikes || 0 }}
              </el-button>
              <span class="message-time">{{ formatTime(message.timestamp) }}</span>
            </div>
          </div>
        </div>

        <!-- 加载指示器 -->
        <div class="loading-indicator" v-if="isLoading">

          <span>AI正在思考...</span>
        </div>
      </div>

    </el-main>

    <!-- 输入区域 -->
    <el-footer class="chat-footer">
      <div class="input-container">
        <el-input
          type="textarea"
          :rows="2"
          :autosize="{ minRows: 2, maxRows: 6 }"
          placeholder="输入您的问题..."
          v-model="inputMessage"
          @keyup.enter.native="sendMessage"
          :disabled="isLoading"
          ref="inputArea"
        >
        </el-input>
        <div class="input-actions">
          <el-button
            type="primary"
            :loading="isLoading"
            @click="sendMessage"
            :disabled="!inputMessage.trim()"
          >
            发送
          </el-button>
        </div>
      </div>
      <div class="footer-notice">
        <span>DeepSeek Chat 可能会产生不准确的信息,请谨慎验证</span>
      </div>
    </el-footer>

  </div>
</template>

<script>

import { marked } from 'marked'
import DOMPurify from 'dompurify'

export default {
  name: 'ChatPage',
  data() {
    return {
      messages: [],//聊天信息
      inputMessage: '',//输入框内容
      isLoading: false,//Ai是否正在加载和发送按钮状态
      showSettings: false, //设置面板,没有使用
      quickQuestions: [ //快捷问题
        "如何学习Vue.js?",
        "用户88购买得最多的商品前三",
        "销量前十的品牌",
        "解释一下量子计算的基本概念"
      ],
      responseLength: 3,
      enableWebSearch: false
    }
  },
  methods: {

    sendMessage() {//发送信息
      if (!this.inputMessage.trim() || this.isLoading) return //判断是否有值

      //构建用户问题的对象
      const userMessage = {
        role: 'user',
        content: this.inputMessage,
        timestamp: new Date()
      }
      //添加用户聊天信息
      this.messages.push(userMessage)
      //清空输入框
      this.inputMessage = ''
      //设置发送按钮是禁止和 AI加载状态为true
      this.isLoading = true

      // 滚动到底部
      this.$nextTick(() => {
        this.scrollToBottom()
      })
      const self = this;
      // this.$http.post("/api/user-info/chat",{"question":userMessage.content})
      this.$http.get("/api/chat/",{params:{"question":userMessage.content}})
        .then(function (rs){
          if(rs.data.code === 200){
            //构建AI回复的消息对象
            const aiMessage = {
              role: 'assistant',//表示AI回复
              content: rs.data.data,// 内容
              timestamp: new Date()
            }
            //添加AI回复到聊天记录里
            self.messages.push(aiMessage);
            //设置发送按钮是允许和 AI加载状态为false
            self.isLoading = false;

          }
        })

    },
    sendQuickQuestion(question) {
      this.inputMessage = question
      this.sendMessage()
    },
    resetConversation() {
      this.$confirm('确定要开始新的对话吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {

        this.messages = []
      })
    },
    scrollToBottom() {
      const container = this.$refs.messageContainer
      container.scrollTop = container.scrollHeight
    },
    formatMessage(content) {
      // 使用marked解析markdown并净化HTML
      return DOMPurify.sanitize(marked.parse(content || ''))
    },
    formatTime(timestamp) {
      return new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
    },
    copyToClipboard(text) {
      navigator.clipboard.writeText(text).then(() => {
        this.$message.success('已复制到剪贴板')
      })
    },
    rateMessage(index, type) {
      const message = this.messages[index]

      if (type === 'like') {
        if (message.rating === 'like') {
          message.rating = null
          message.likes = (message.likes || 1) - 1
        } else {
          if (message.rating === 'dislike') {
            message.dislikes = (message.dislikes || 1) - 1
          }
          message.rating = 'like'
          message.likes = (message.likes || 0) + 1
        }
      } else {
        if (message.rating === 'dislike') {
          message.rating = null
          message.dislikes = (message.dislikes || 1) - 1
        } else {
          if (message.rating === 'like') {
            message.likes = (message.likes || 1) - 1
          }
          message.rating = 'dislike'
          message.dislikes = (message.dislikes || 0) + 1
        }
      }
    },
    focusInput() {
      this.$refs.inputArea.focus()
    }
  },
  mounted() {
    this.focusInput()
  }
}
</script>

<style scoped>
.chat-container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f7fa;
}

.chat-header {
  background-color: #ffffff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  padding: 0 20px;
}

.header-content {
  height: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
  width: 100%;
}

.logo {
  display: flex;
  align-items: center;
  font-size: 18px;
  font-weight: bold;
}

.logo img {
  margin-right: 10px;
  border-radius: 50%;
}

.header-actions .el-button {
  margin-left: 10px;
}

.chat-main {
  flex: 1;
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.message-container {
  flex: 1;
  overflow-y: auto;
  padding: 20px;
  max-width: 900px;
  margin: 0 auto;
  width: 100%;
}

.welcome-message {
  text-align: center;
  padding: 40px 0;
  color: #606266;
}

.welcome-message h2 {
  font-size: 24px;
  margin-bottom: 16px;
}

.welcome-message p {
  font-size: 16px;
  margin-bottom: 24px;
}

.quick-questions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}

.quick-questions .el-button {
  margin: 0 5px 5px 0;
}

.message-item {
  display: flex;
  margin-bottom: 20px;
}

.message-avatar {
  margin-right: 15px;
}

.message-avatar img {
  width: 40px;
  height: 40px;
  border-radius: 50%;
}

.message-content {
  flex: 1;
  max-width: calc(100% - 55px);
}

.message-text {
  padding: 12px 16px;
  border-radius: 8px;
  line-height: 1.6;
  word-wrap: break-word;
}

.user-message .message-text {
  background-color: #e6f7ff;
  border: 1px solid #91d5ff;
}

.ai-message .message-text {
  background-color: #ffffff;
  border: 1px solid #ebeef5;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.message-actions {
  margin-top: 8px;
  display: flex;
  align-items: center;
  font-size: 12px;
  color: #909399;
}

.message-actions .el-button {
  padding: 0;
  margin-right: 10px;
  color: #909399;
}

.message-actions .el-button.active {
  color: #409eff;
}

.message-time {
  margin-left: auto;
}

.loading-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 15px;
  color: #909399;
}

.loading-indicator .el-icon {
  margin-right: 8px;
  animation: rotating 2s linear infinite;
}

@keyframes rotating {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.chat-footer {
  padding: 0;
  background-color: #ffffff;
  box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.1);
}

.input-container {
  max-width: 700px;
  margin: 0 auto;
  padding: 15px;
  width: 100%;
}

.input-tools {
  margin-bottom: 5px;
}

.input-tools .el-button {
  padding: 0;
  margin-right: 10px;
}

.input-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}

.footer-notice {
  text-align: center;
  padding: 10px;
  font-size: 12px;
  color: #909399;
  border-top: 1px solid #ebeef5;
}

/* Markdown内容样式 */
.message-text :deep(pre) {
  background-color: #f6f8fa;
  padding: 12px;
  border-radius: 6px;
  overflow-x: auto;
}

.message-text :deep(code) {
  background-color: #f6f8fa;
  padding: 2px 4px;
  border-radius: 3px;
  font-family: monospace;
}

.message-text :deep(blockquote) {
  border-left: 3px solid #dfe2e5;
  color: #6a737d;
  padding-left: 12px;
  margin-left: 0;
}

.message-text :deep(ul),
.message-text :deep(ol) {
  padding-left: 20px;
}

.message-text :deep(table) {
  border-collapse: collapse;
  width: 100%;
  margin: 12px 0;
}

.message-text :deep(th),
.message-text :deep(td) {
  border: 1px solid #dfe2e5;
  padding: 6px 13px;
}

.message-text :deep(th) {
  background-color: #f6f8fa;
}
</style>

整个 chat 文件夹长这样:

将前端与后端一起启动后,直接进入 chat 页面,开始聊天问答:

八、项目展示

其实还应该有登录演示,但是这里就不用展示了,就是平常的密码登录或者邮箱验证码登录,下面直接演示聊天问答:

可以看到,普通的聊天和问答,以及基于知识库的问题大模型都可以做出回答,说明基本是没问题了。但其实后面我们还写了一个智能体是关于注册系统的,用户第一次登录系统的话,数据库里没有他的用户名和密码嘛,注册的时候就会调用智能体然后自动将数据插入到后台的数据库,这样第二次登录系统的时候就可以直接登录了。

以上就是我们整个项目的展示,后期加的一些功能虽然没有没有写上来,但是目前的也够用了,有问题可以指出 (๑•̀ㅂ•́)و✧

相关推荐
NAGNIP1 天前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab1 天前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab1 天前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
林小帅1 天前
【笔记】OpenClaw 架构浅析
前端·agent
林小帅1 天前
【笔记】OpenClaw 生态系统的多语言实现对比分析
前端·agent
AngelPP1 天前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年1 天前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼1 天前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS1 天前
Kimi Chat Completion API 申请及使用
前端·人工智能
warm3snow1 天前
Claude Code 黑客马拉松:5 个获奖项目,没有一个是"纯码农"做的
ai·大模型·llm·agent·skill·mcp