支持私有化部署
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


基于LangChain+LangGraph+LangSmith+Streamlit开发带Web页面交互的Agent

发布日期:2025-08-09 10:56:07 浏览次数: 1526
作者:弓长先生的杂货铺

微信搜一搜,关注“弓长先生的杂货铺”

推荐语

打造高效易用的SQL查询Agent:基于LangChain生态实现自然语言交互与可视化调试。

核心内容:
1. 项目目标:构建支持自然语言查询SQLite的Agent,集成Web UI与调试工具
2. 技术架构:LangGraph状态机管理多步流程,LangSmith实现可视化追踪
3. 开发优势:框架原生能力大幅降低代码量,标准化封装提升稳定性

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家

在之前的文章中,我介绍了一种基于 LangChain Core实现 Agent 的开发思路。这个 Agent 能实现基本的自然语言查询能力,但只支持 CLI 运行,用户界面不够友好。

此外,当时使用了 LangChain 自带的 PromptTemplate来手动实现 ReAct 流程,整个逻辑偏重流程控制,代码量较多,调试体验也不理想。为解决 LLM 和 Agent 的交互调试问题,我甚至还手动实现了一套日志系统。

后来在调研 Google ADK 框架时,我发现它的生态非常完善,像提示词管理、ReAct 流程、日志追踪等功能都有原生支持,基本开箱即用,开发效率大幅提升。基于 ADK 框架开发的 Agent 代码量也很低,代码结构非常简洁,开发人员只需要编写最关键的应用层核心逻辑即可。

受到 ADK 架构的启发,我开始思考能否在 LangChain 生态中复现类似的工程结构。经过一番研究,我发现其实也可以通过 LangChain 的多个组件组合出一套体验不错的方案。现分享如下。

一、项目目标

本项目旨在实现一个支持 自然语言查询 SQLite 数据库的 LLM Agent,并具备以下几个特性:

1. 支持自然语言对话查询(输入 SQL 意图,输出结果)

2. 提供面向 C 端用户的 Web UI(基于 Streamlit)

3. 提供开发人员可调试的 Web UI(基于 LangSmith)

4. 尽量使用框架原生能力,降低代码量

二、整体架构与核心组件

为达成上述目标,我选用了 LangChain 生态中的以下关键组件:

1. LangChain Core(必选)

LangChain Core 提供了整个 Agent 运行的核心框架。上篇文章中,Agent实现、Tool筛选、SQLite访问、LLM交互等基础功能均基于该核心包实现。此次实践中,我们仅保留SQLite和LLM原生包的使用,其余功能交由 LangGraph实现。

2. LangGraph(必选)

LangGraph 是 LangChain 推出的轻量级状态机框架,适合实现可控、可调度的多步 Agent 流程。相比传统 Chain 模型,它更适合管理带有中间状态的多轮对话和任务执行路径。

在本项目中,我使用 LangGraph 管理 Agent 的执行节点,明确分离“意图解析、工具选择、结果生成”等步骤。

同时,由于 LangGraph 自带 ReAct 框架,也省去了我们通过 PromptTemplate 实现 ReAct 流程的代码。不但降低了代码量,更通过标准化的封装降低了代码出错的概率。

3. LangSmith(必选)

LangSmith 提供了一套非常强大的 LLM 调试与可视化工具,支持:

1) 对 Agent 执行过程的可视化追踪

2) 对提示词和模型响应的逐步分析

3) 快速定位失败样本和异常行为

开发过程中,我们通过 LangSmith 实现了 Agent 开发的可追溯性和交互透明性,大幅提升了调试效率。

但必须要注意的是,区别于 ADK 自带的 Web 调试功能,LangSmith 是一个部署在云端的Web环境,对于使用有一定的免费额度,超过额度需要收费。本质上,其还是个商用的收费工具。

相关LangChain组件的生态

LangSmith收费信息:https://www.langchain.com/pricing

4. Streamlit(必选)

Streamlit 是一个轻量级 Web UI 框架,适合快速构建原型。我用它来为 Agent 构建面向 C 端用户的交互界面,实现文本输入、流式输出、执行日志展示等功能。

相比传统的前端组合,Streamlit 更快更轻,能快速构建 MVP。

5. 工具预筛选机制(可选)

对于多工具 Agent,可以通过提示词约束或函数方式对工具进行预筛选,减少模型决策负担,提高执行准确率。

在实际的使用中,我发现筛选后的 Tool 数量在小于4个时候会影响 LLM 的判断,降低 LLM 最终选择工具的效果。甚至极端情况下,筛选后的 Tool 不能承担起执行任务的能力,造成 LLM 陷入无限循环选择的情况。即使小于4个的 Tool能够被 LLM正确识别,可能多 Agent 实现才是一个更合理的设计思路。 

当在 Tool 总体数量不多的情况下,该预选机制的效果比较有限。比如 Tool 总量为6个,假设预筛选保留4个结果传给 LLM,那么其实也就节省了2个 Tool 的描述Token,但其引入的embedding会增加代码复杂度。

基于以上分析,如果没有很强烈的理由,我个人不是很推荐使用工具预筛选机制。

三、关键实践
1. 代码结构
此次修改后代码结构更加清晰,代码量从原3000行降低到了600行:
langchain_agent/├── __init__.py├── agents/│   ├── __init__.py│   └── sql_agent.py          # SQL Agent 实现├── config/│   ├── __init__.py│   ├── settings.py          # 应用设置│   ├── llm_config.py        # LLM 配置│   └── database_config.py   # 数据库配置└── utils/    ├── __init__.py    ├── embeddings.py        # 嵌入模型工具(非必须)    └── retrieval.py         # 工具检索功能(非必须)
2. 环境设置
1)依赖包
# Core dependencieslangchain>=0.1.0langchain-community>=0.0.20langchain-core>=0.1.0langchain-DeepSeek>=0.1.0langgraph>=0.1.0python-dotenv>=1.0.0faiss-cpu>=1.7.0
# Streamlit supportstreamlit>=1.28.0
2)配置信息
# LangChain Agent 环境配置# ===== LLM 配置 =====DEEPSEEK_API_KEY=# LLM模型配置LLM_MODEL=deepseek-chatLLM_TEMPERATURE=0.0LLM_MAX_TOKENS=4000LLM_TIMEOUT=60
# ===== 数据库配置 =====# 数据库文件路径 DATABASE_PATH=DATABASE_TYPE=sqlite
# LangChain监控配置# LangSmith集成 - 零配置监控LANGCHAIN_TRACING_V2=trueLANGCHAIN_API_KEY=LANGCHAIN_PROJECT=sqlite-agentLANGCHAIN_ENDPOINT=https://api.smith.langchain.com
3. Agent 实现关键模块
1)数据库初始化
from langchain_community.utilities.sql_database import SQLDatabasefrom langchain_agent.config.settings import get_settings
    if config is None:        settings = get_settings()        config = DatabaseConfig(            database_path=settings.database_path,            database_type=settings.database_type        )
    config.validate()
    try:        db = SQLDatabase.from_uri(            config.database_uri,            **config.connection_params        )        return db
2)LLM 初始化
from langchain_deepseek import ChatDeepSeekfrom langchain_agent.config.settings import get_settings
    if config is None:        settings = get_settings()        config = LLMConfig(            api_key=settings.deepseek_api_key,            model=settings.llm_model,            temperature=settings.llm_temperature,            max_tokens=settings.llm_max_tokens,            timeout=settings.llm_timeout        )
    try:        llm = ChatDeepSeek(            api_key=config.api_key,            model=config.model,            temperature=config.temperature,            max_tokens=config.max_tokens,            timeout=config.timeout,            **config.extra_params        )        return llm
3)创建数据库连接
        # 创建数据库连接        from langchain_agent.config.database_config import DatabaseConfig        db_config = DatabaseConfig(            database_path=self.database_path,            database_type=self.settings.database_type        )        self.db = create_database_connection(db_config)
4)创建 LLM
        # 创建LLM        self.llm = llm if llm else create_llm()
5)注册 SQL 工具包
采用SQLDatabaseToolkit自带工具包,不需要再编写额外的工具描述。
        from langchain_community.agent_toolkits import SQLDatabaseToolkit        # 创建SQL工具包        self.toolkit = SQLDatabaseToolkit(db=self.db, llm=self.llm)        self.all_tools = self.toolkit.get_tools()
6)创建系统提示词
self.system_message = self._create_system_message()

    def _create_system_message(self) -> str:        """创建系统提示"""        return """You are an agent designed to interact with a SQL database.Given an input question, create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.Unless the user specifies a specific number of examples they wish to obtain, always limit your query to at most {top_k} results.You can order the results by a relevant column to return the most interesting examples in the database.Never query for all the columns from a specific table, only ask for the relevant columns given the question.You MUST double check your query before executing it. If you get an error while executing a query, rewrite the query and try again.DO NOT make any DML statements (INSERT, UPDATE, DELETE, DROP etc.) to the database.To start you should ALWAYS look at the tables in the database to see what you can query. Do NOT skip this step.Then you should query the schema of the most relevant tables.""".format(            dialect=self.db.dialect,            top_k=10        )
7)创建 Agent
        try:            self.agent = create_react_agent(                self.llm,                self.current_tools,                prompt=self.system_message            )
from langgraph.prebuilt import create_react_agent
def create_sql_agent(    database_path: Optional[str] = None,    llm: Optional[BaseLanguageModel] = None) -> SQLAgent:
    return SQLAgent(        database_path=database_path,        llm=llm    )
8) 实现查询功能
from langchain_core.messages import HumanMessage
    def query(self, question: str, user_id: Optional[str] = None) -> str:        """        执行SQL查询
        Args:            question: 用户问题            user_id: 用户ID(用于监控)
        Returns:            str: 查询结果        """        try:            config = self._prepare_query_config(question, user_id, "single")            result = self.agent.invoke({                "messages": [HumanMessage(content=question)]            }, config=config)
            # 提取最后一条AI消息作为答案            messages = result.get("messages", [])            for message in reversed(messages):                if hasattr(message, 'type'and message.type == 'ai':                    return message.content
            return "未能获得查询结果"
        except Exception as e:            return f"查询执行失败: {str(e)}"
9)集成streamlit
import streamlit as st
    # 用户输入    if prompt := st.chat_input("输入你的问题..."):        # 添加用户消息        st.session_state.messages.append({"role": "user""content": prompt})        with st.chat_message("user"):            st.markdown(prompt)
        # 生成回复        with st.chat_message("assistant"):            with st.spinner("正在思考..."):                try:                    response = st.session_state.agent.query(prompt)
                    st.markdown(response)                    st.session_state.messages.append({"role": "assistant""content": response})
                except Exception as e:                    error_msg = f"查询出错: {str(e)}"                    st.error(error_msg)                    st.session_state.messages.append({"role": "assistant""content": error_msg})
10)运行命令
streamlit run app.py
访问地址:http://localhost:8501
4. 实际效果
1)streamlit
streamlit的Web UI虽然比较简洁,但体验也远比Cli要好。
2)LangSmith
LangSmith提供的功能和 ADK Web 有点像。能够看到此次查询 Agent 和 LLM 之间交互了3次,耗时19.15秒,消耗3397个Token。
左侧的调用链显示了整个交互流程,点击后能够看到更加详细的信息,比如:原始交互信令、工具调用等。
四、总结

通过将 LangChain 的多组件组合使用,我们可以构建出一套完整的、带 Web UI 的可交互 Agent 系统。它在功能结构上与 Google ADK 架构类似,但更开放、更灵活。

整体开发体验上,LangGraph + LangSmith 负责调度与可视化,Streamlit 提供界面,LangChain Core 提供核心逻辑——各自职责清晰,组合方式灵活,极大提升了工程效率。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询