ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LangChain社区工具实战:SQLDatabase、DuckDuckGo与LangGraph的Agent开发指南

LangChain社区工具实战:SQLDatabase、DuckDuckGo与LangGraph的Agent开发指南 1. 从一次“翻社区翻到凌晨两点”说起LangChain 这个生态有个特点官方文档写得规规矩矩但真正有意思的东西往往藏在社区仓库、示例目录、以及各种“顺手做出来”的小工具里。我最初接触 LangChain 是为了搭一个能查数据库、能联网搜索的问答机器人结果文档看了三天代码跑通了但总觉得少了点什么——直到我开始翻它的社区集成列表和周边项目才发现原来这里藏着一堆“看起来不起眼、用起来真香”的工具。这篇内容就是把我这段时间挖到的东西整理出来。核心围绕几个关键词LangChain、Agent、SQLDatabase、DuckDuckGo、LangGraph。如果你正在做 Agent 开发或者刚入门 LangChain 想找点能直接上手的实战案例又或者你在纠结 LangChain、Dify、CrewAI 这些框架到底怎么选那这篇应该能给你一些参考。我不会只列工具名而是会把每个工具解决什么问题、怎么用、坑在哪都尽量讲清楚。先说结论LangChain 社区里真正有价值的不是那些“大而全”的框架宣传而是那些针对具体场景做深做透的小工具。比如SQLDatabaseToolkit让 Agent 直接跟数据库对话DuckDuckGoSearchRun让 Agent 不依赖付费 API 就能联网LangGraph则把 Agent 的状态管理从“黑盒”变成了“白盒”。这些东西单独看都不复杂但组合起来能解决很多实际问题。下面我会分几个部分展开先讲清楚 LangChain 社区工具的整体生态和选型逻辑然后重点拆解 SQLDatabase 和 DuckDuckGo 这两个最常用的工具接着聊 LangGraph 在 Agent 编排里的实际价值最后分享一些我在 Agent 开发中踩过的坑和总结的经验。整篇内容会尽量贴近实战代码示例都是可以直接跑的。2. LangChain 社区工具生态的真实面貌2.1 为什么社区工具比官方文档更值得挖LangChain 的官方文档有个问题它要照顾太多场景所以每个功能都讲得比较“标准”但实际项目里遇到的问题往往不标准。比如你想让 Agent 查数据库官方文档会告诉你用SQLDatabaseToolkit但不会告诉你当数据库有几十张表的时候Agent 会疯狂选错表也不会告诉你DuckDuckGoSearchRun在某些网络环境下会超时需要加重试逻辑。社区工具的价值就在这里它们是被人“用出来”的不是“设计出来”的。每个工具背后通常都有一个具体的痛点。比如LangGraph的出现就是因为很多人发现用AgentExecutor做多步骤任务时状态完全不可控出了问题只能靠日志猜。LangGraph 把 Agent 的执行过程拆成图节点每个节点的输入输出都可见这才让调试变得可能。我自己的习惯是先看官方文档了解基本概念然后直接去翻 LangChain 的langchain_community包里面按功能分类了几百个工具。每个工具的源码都不长读一遍基本就知道它能干什么、怎么用。比看文档快得多。2.2 社区工具的分类逻辑与选型思路LangChain 社区工具大致可以分成几类数据连接类SQLDatabase、PandasDataFrame、CSVLoader 等负责把外部数据接进来。搜索与信息获取类DuckDuckGoSearchRun、WikipediaQueryRun、ArxivQueryRun 等让 Agent 能获取实时信息。执行与操作类PythonREPLTool、ShellTool、RequestsTool 等让 Agent 能执行代码或调用 API。编排与状态管理类LangGraph、AgentExecutor、PlanAndExecute 等负责控制 Agent 的执行流程。选型的时候我一般会问三个问题第一这个工具解决的是“数据接入”还是“流程控制”第二它依赖外部服务吗依赖的话稳定性和成本如何第三它的输出格式是否适合直接喂给 LLM举个例子DuckDuckGoSearchRun和GoogleSerperRun都能做搜索但前者免费、无需 API Key后者需要付费且要配置密钥。如果你只是做原型验证DuckDuckGo 完全够用但如果要做生产级应用就得考虑搜索结果的稳定性和覆盖率可能需要换更专业的搜索 API。再比如SQLDatabaseToolkit和直接写 SQL 查询前者让 Agent 自己决定查什么表、怎么写 SQL适合探索性查询后者适合固定报表场景。选哪个取决于你的业务是否需要“动态决策”。2.3 一个容易被忽略的细节工具的描述文本LangChain 里每个工具都有一个description字段这个字段不是给人看的是给 LLM 看的。Agent 在选择工具时会读取这个描述来判断“这个工具能不能解决当前问题”。所以描述写得好不好直接决定 Agent 的工具选择准确率。我见过很多人直接从文档复制示例代码工具描述就写一句“查询数据库”。结果 Agent 经常在不需要查数据库的时候也去调这个工具。后来我把描述改成“当用户询问具体数据、统计信息、或需要从结构化表中检索记录时使用此工具。输入应该是自然语言问题不要直接写 SQL。”准确率立刻上去了。这个细节在官方文档里几乎没提但实际用起来影响很大。社区里有些工具的描述写得非常讲究比如DuckDuckGoSearchRun的描述是“A wrapper around DuckDuckGo Search. Useful for when you need to answer questions about current events.” 这句话明确告诉 LLM我是用来查实时信息的不是用来查静态知识的。这种描述方式值得学习。3. SQLDatabaseToolkit让 Agent 直接跟数据库对话3.1 它到底解决了什么问题传统做法里如果要让 LLM 查数据库你得先写一个函数接收用户问题然后自己拼 SQL再执行再把结果喂给 LLM 总结。这个过程里SQL 是你写的LLM 只负责最后一步。但SQLDatabaseToolkit把这个过程反过来了LLM 自己决定查哪张表、怎么写 SQL、要不要先看看表结构。这听起来很美好但实际用起来有几个关键点要注意。第一数据库的 schema 信息怎么传给 LLM如果表很多全部塞进 prompt 会超 token 限制。第二LLM 写的 SQL 可能出错怎么处理第三查询结果可能很大怎么截断SQLDatabaseToolkit的设计是它提供了一组工具包括ListTables、GetTableSchema、QuerySQL等。Agent 可以先调ListTables看有哪些表再调GetTableSchema看某张表的结构最后调QuerySQL执行查询。这个过程是逐步的不是一次性把所有信息塞给 LLM。我实测下来这种方式在表数量少于 20 张的时候效果很好。超过 20 张Agent 容易在选表阶段就迷失。这时候需要额外做一层“表筛选”比如先用一个轻量模型根据用户问题筛选出可能相关的几张表再让 Agent 在这些表里操作。3.2 最小可运行示例与关键配置下面是一个可以直接跑的示例。假设你有一个 SQLite 数据库里面有一张employees表from langchain_community.utilities import SQLDatabase from langchain_community.agent_toolkits import SQLDatabaseToolkit from langchain_openai import ChatOpenAI from langchain.agents import create_sql_agent # 连接数据库 db SQLDatabase.from_uri(sqlite:///company.db) # 初始化 LLM llm ChatOpenAI(modelgpt-4, temperature0) # 创建工具包 toolkit SQLDatabaseToolkit(dbdb, llmllm) # 创建 Agent agent create_sql_agent( llmllm, toolkittoolkit, verboseTrue, handle_parsing_errorsTrue ) # 运行 result agent.invoke(公司里工资最高的五个人是谁) print(result)这段代码里handle_parsing_errorsTrue很关键。LLM 有时候会输出格式不对的内容导致解析失败。加上这个参数后LangChain 会把错误信息返回给 LLM让它重新生成。实测能显著提高成功率。另一个关键配置是db的初始化参数。SQLDatabase.from_uri支持include_tables和ignore_tables可以控制哪些表对 Agent 可见。如果你的数据库有敏感表一定要用include_tables明确指定不要用默认的全表可见。3.3 踩坑记录当 Agent 选错表的时候我遇到过一个典型问题数据库里有orders和order_items两张表用户问“上个月销售额是多少”Agent 有时候只查orders表有时候只查order_items表结果都不对。正确的做法是 join 两张表。排查过程是这样的我先打开verboseTrue看 Agent 的思考过程。发现它在GetTableSchema阶段只看了orders表然后就急着写 SQL 了。问题出在工具描述上——GetTableSchema的描述没有强调“如果涉及多个表需要分别查看每个表的结构”。后来我做了两件事第一修改工具描述明确提示“在写 SQL 之前确保你已经查看了所有相关表的结构”。第二在系统提示里加了一句“如果问题涉及金额、订单、商品等多个实体考虑是否需要 join 多张表”。改完之后Agent 的 join 正确率从大概 60% 提升到了 90% 以上。这个经验说明SQLDatabaseToolkit本身不复杂但它的效果高度依赖提示词和工具描述。不要指望开箱即用就能达到生产级准确率一定要根据你的数据库特点做调优。3.4 性能与成本控制别让 Agent 把数据库查爆Agent 查数据库有个隐患它可能会写出没有LIMIT的查询或者在大表上做全表扫描。我见过一次 Agent 对一个千万行级别的表执行SELECT *直接把数据库连接池占满了。控制方法有几个第一在SQLDatabase初始化时设置sample_rows_in_table_info限制返回给 LLM 的样本行数。第二在工具层面加一个 SQL 校验拒绝没有LIMIT的查询。第三用只读账号连接数据库从权限层面杜绝写操作。db SQLDatabase.from_uri( sqlite:///company.db, include_tables[employees, departments], sample_rows_in_table_info3 )sample_rows_in_table_info3表示每张表只返回 3 行样本数据给 LLM 看。这个值不要设太大否则 prompt 会很长也不要设太小否则 LLM 可能误解字段含义。3 到 5 行是比较合适的范围。另外如果你的数据库是 PostgreSQL 或 MySQL建议单独建一个只读用户给 Agent 用。SQLite 的话可以复制一份数据库文件让 Agent 只操作副本。这样即使 Agent 写出DELETE或UPDATE也不会影响生产数据。4. DuckDuckGoSearchRun不花钱也能让 Agent 联网4.1 为什么选 DuckDuckGo 而不是其他搜索工具LangChain 支持的搜索工具很多Google Serper、Bing Search、Tavily、DuckDuckGo 等。DuckDuckGo 的优势很明显免费、不需要 API Key、隐私友好。对于原型验证和个人项目来说这是最省事的选择。但它的缺点也很明显搜索结果的质量和覆盖率不如 Google而且没有官方 APILangChain 是通过抓取网页的方式实现的。这意味着如果 DuckDuckGo 的页面结构变了工具可能会失效。另外频繁请求可能会被限流。我的建议是开发阶段用 DuckDuckGo快速验证 Agent 的搜索能力上线前换成有官方 API 的搜索服务比如 Tavily 或 Serper。这样既省了开发阶段的成本又保证了生产环境的稳定性。4.2 基础用法与结果处理from langchain_community.tools import DuckDuckGoSearchRun search DuckDuckGoSearchRun() result search.invoke(LangChain 最新版本有哪些新功能) print(result)返回的结果是一个字符串包含搜索结果的摘要。注意它返回的不是结构化的 JSON而是拼接好的文本。如果你需要更结构化的结果可以用DuckDuckGoSearchResults它返回的是包含标题、链接、摘要的列表。from langchain_community.tools import DuckDuckGoSearchResults search DuckDuckGoSearchResults() results search.invoke(LangChain LangGraph 教程) for r in results: print(r)实际用的时候我一般会把搜索结果直接喂给 LLM 做总结。但要注意搜索结果可能包含大量无关信息最好在 prompt 里加一句“只根据以下搜索结果回答如果搜索结果不包含答案就说不知道”。这样能减少 LLM 胡编乱造的情况。4.3 把搜索工具塞进 Agent 的正确姿势单独用搜索工具很简单但把它和 Agent 结合的时候有几个细节要注意。第一搜索工具的调用频率。Agent 可能会连续调用多次搜索如果每次都请求 DuckDuckGo很容易被限流。可以在工具外面包一层缓存相同查询直接返回缓存结果。第二搜索结果的截断。DuckDuckGo 返回的结果可能很长全部塞进 prompt 会浪费 token。我一般会截取前 2000 个字符或者只保留前 5 条结果。第三错误处理。网络请求可能超时或失败工具需要能优雅地返回错误信息而不是直接抛异常。LangChain 的工具默认会捕获异常并返回错误字符串但你可以自定义这个行为。from langchain_community.tools import DuckDuckGoSearchRun from langchain_core.tools import tool import requests tool def safe_search(query: str) - str: 当需要查询实时信息时使用此工具。输入应该是搜索关键词。 try: search DuckDuckGoSearchRun() result search.invoke(query) return result[:2000] except Exception as e: return f搜索失败{str(e)}请尝试换个关键词或稍后重试。这个safe_search工具做了三件事截断结果、捕获异常、返回友好的错误信息。Agent 收到错误信息后可以决定是重试还是换一种方式回答。4.4 实测中的意外情况与应对有一次我让 Agent 查“今天的天气”DuckDuckGo 返回了一堆天气预报网站的链接但摘要里没有具体温度。Agent 拿着这些摘要硬是编了一个“今天气温 25 度”的答案。这就是典型的“搜索结果不包含答案但 LLM 强行回答”的情况。应对方法是在系统提示里明确写“如果搜索结果中没有明确包含用户问题的答案不要猜测直接告诉用户你没有找到相关信息。”另外可以在 Agent 的输出解析阶段加一个校验如果答案里包含具体数字但搜索结果里没有对应数字就标记为“可能不准确”。还有一个坑是 DuckDuckGo 的地区限制。某些查询在不同地区返回的结果不一样。如果你做的是面向特定地区的应用最好在查询里加上地区关键词比如“北京 天气”而不是“天气”。5. LangGraph把 Agent 的状态管理从黑盒变白盒5.1 AgentExecutor 的局限性在哪里用AgentExecutor的时候Agent 的执行过程是一个循环思考、选择工具、执行工具、观察结果、再思考……直到得出最终答案。这个循环对用户来说是黑盒你只能看到最终的输出中间发生了什么全靠verbose日志。这在简单场景下没问题但一旦任务变复杂问题就来了。比如你想让 Agent 先查数据库再根据结果搜索相关信息最后汇总成报告。用AgentExecutor的话你没法控制它先做什么后做什么也没法在中间步骤插入人工审核。LangGraph解决的就是这个问题。它把 Agent 的执行过程建模成一张图每个节点是一个操作比如调用 LLM、执行工具每条边是节点之间的流转条件。你可以精确控制每一步做什么也可以在某些节点后暂停等待人工输入。5.2 用 LangGraph 重构一个多步骤 Agent假设我们要做一个“竞品分析”Agent第一步搜索竞品的最新动态第二步查询内部数据库里的竞品销售数据第三步汇总成报告。用 LangGraph 可以这样写from langgraph.graph import StateGraph, END from typing import TypedDict, Annotated import operator class AgentState(TypedDict): messages: Annotated[list, operator.add] search_results: str sales_data: str report: str def search_node(state: AgentState): # 调用搜索工具 search DuckDuckGoSearchRun() results search.invoke(竞品最新动态) return {search_results: results} def query_db_node(state: AgentState): # 查询数据库 db SQLDatabase.from_uri(sqlite:///sales.db) # ... 执行查询 return {sales_data: 查询结果} def generate_report_node(state: AgentState): # 汇总生成报告 llm ChatOpenAI(modelgpt-4) prompt f根据以下信息生成竞品分析报告\n搜索动态{state[search_results]}\n销售数据{state[sales_data]} report llm.invoke(prompt) return {report: report.content} # 构建图 workflow StateGraph(AgentState) workflow.add_node(search, search_node) workflow.add_node(query_db, query_db_node) workflow.add_node(generate_report, generate_report_node) workflow.set_entry_point(search) workflow.add_edge(search, query_db) workflow.add_edge(query_db, generate_report) workflow.add_edge(generate_report, END) app workflow.compile() result app.invoke({messages: []}) print(result[report])这个例子里每个节点只做一件事节点之间的顺序是固定的。你可以在任何节点后加一个“人工审核”节点等人工确认后再继续。这种可控性是用AgentExecutor很难做到的。5.3 状态管理与条件分支的实际价值LangGraph 真正强大的地方在于条件分支。比如上面的例子如果搜索失败可以走另一条路径先跳过搜索只根据数据库数据生成报告并在报告里注明“搜索数据缺失”。def should_continue(state: AgentState): if state[search_results] and 失败 not in state[search_results]: return query_db else: return generate_report workflow.add_conditional_edges( search, should_continue, { query_db: query_db, generate_report: generate_report } )这种条件分支在AgentExecutor里只能靠 LLM 自己判断但在 LangGraph 里是显式定义的。这意味着你可以精确控制 Agent 的行为而不是祈祷 LLM 做出正确决策。我实测下来LangGraph 的学习曲线比AgentExecutor陡一些但一旦掌握调试效率会高很多。因为每个节点的输入输出都可以单独测试出了问题能快速定位是哪个环节的错。5.4 和 CrewAI、Dify 这些框架的对比经常有人问LangChain、LangGraph、CrewAI、Dify 到底怎么选我的看法是LangChain适合需要深度定制、对底层控制要求高的场景。它的工具生态最丰富但抽象层多学习成本不低。LangGraph适合需要精确控制 Agent 执行流程的场景。它不替代 LangChain而是补充 LangChain 在流程编排上的不足。CrewAI适合快速搭建多 Agent 协作场景。它的抽象层次更高上手快但定制空间相对小。Dify适合非开发者或想快速做原型的产品经理。它提供可视化界面但底层还是基于 LangChain 那一套。如果你已经熟悉 LangChain想进一步提升 Agent 的可控性LangGraph 是最自然的选择。如果你刚开始做 Agent想快速看到效果CrewAI 或 Dify 可能更合适。没有绝对的“哪个好”只有“哪个更适合当前阶段”。6. Agent 开发中那些文档不会告诉你的经验6.1 工具描述比工具本身更重要前面提过工具描述的重要性这里再展开说一下。LangChain 的工具描述是 LLM 选择工具的唯一依据。如果描述写得模糊LLM 就会乱选。我总结了一个写描述的模板当[具体场景]时使用此工具。输入应该是[输入格式]不要[错误用法]。输出包含[输出内容]。比如一个查询天气的工具描述可以写成“当用户询问某个城市的天气时使用此工具。输入应该是城市名称不要包含‘天气’这个词。输出包含温度和天气状况。”这种描述方式比“查询天气”四个字有效得多。实测下来工具选择准确率能提升 30% 以上。6.2 记忆管理别让对话历史撑爆上下文Agent 的记忆管理是个容易被忽视的问题。默认情况下LangChain 会把所有对话历史都塞进 prompt几轮对话之后 token 就爆了。解决方案有几种滑动窗口只保留最近 N 轮对话。摘要记忆用 LLM 把历史对话总结成一段话。向量记忆把历史对话存进向量数据库按需检索。我一般用滑动窗口加摘要的组合最近 5 轮保留原文更早的对话用 LLM 总结成一段话。这样既能保留关键信息又不会让 prompt 太长。from langchain.memory import ConversationSummaryBufferMemory memory ConversationSummaryBufferMemory( llmllm, max_token_limit1000, return_messagesTrue )max_token_limit1000表示当对话历史超过 1000 token 时自动触发摘要。这个值根据你的模型上下文窗口来定一般不要超过窗口大小的三分之一。6.3 错误处理与重试Agent 不是一次就能跑对的Agent 执行过程中可能遇到各种错误工具调用失败、LLM 输出格式不对、网络超时等。如果不做错误处理Agent 可能直接崩溃。LangChain 提供了一些内置的错误处理机制比如handle_parsing_errors但还不够。我的做法是在每个工具外面包一层重试逻辑from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def call_tool_with_retry(tool, input_text): return tool.invoke(input_text)stop_after_attempt(3)表示最多重试 3 次wait_exponential表示重试间隔指数增长。这样能应对大部分临时性错误。另外Agent 的最终输出也要做校验。比如如果 Agent 回答里包含“我不知道”但实际工具返回了有效结果说明 Agent 没有正确使用工具。这时候可以重新调用一次或者在 prompt 里加一句“请根据工具返回的结果回答不要说你不知道”。6.4 成本控制Token 就是钱Agent 的 token 消耗比普通对话高得多因为每次工具调用都要把工具描述、历史对话、工具结果全部塞进 prompt。一个复杂的 Agent 任务可能消耗几万甚至几十万 token。控制成本的方法有几个第一用更小的模型做工具选择用大模型做最终回答。第二精简工具描述去掉不必要的示例。第三限制工具返回结果的长度。第四用缓存避免重复调用。我实测过一个查询数据库的 Agent优化前每次调用消耗约 8000 token优化后降到 3000 左右。主要优化点就是精简了工具描述和限制了返回结果长度。6.5 测试与评估怎么知道 Agent 好不好用Agent 的测试比普通函数复杂得多因为它的输出不是确定性的。同一个问题Agent 可能给出不同的答案。我的做法是建一个测试集包含 20 到 50 个典型问题每个问题有预期答案或预期行为。然后定期跑一遍统计准确率。评估指标包括工具选择是否正确、最终答案是否准确、执行步骤是否合理、token 消耗是否在预算内。这些指标不需要全部自动化但至少要有一个定性的评估。我一般会手动看几个 case 的verbose日志检查 Agent 的思考过程是否合理。如果发现它经常绕弯路就说明提示词或工具描述需要调整。7. 一些零散但有用的补充7.1 关于 LangChain 版本兼容性LangChain 的版本更新很快不同版本之间的 API 可能有变化。我建议在requirements.txt里锁定版本不要用langchain0.1.0这种写法。另外langchain_community和langchain_core是分开的包升级的时候要注意版本匹配。如果遇到ImportError先检查是不是包版本不匹配。我遇到过好几次因为langchain和langchain_community版本不一致导致的奇怪错误。7.2 关于 Agent 的安全边界Agent 能执行代码、查数据库、发网络请求这些能力如果被滥用后果很严重。我建议在生产环境里做几件事第一限制 Agent 能访问的工具不需要的工具不要注册。第二对 Agent 的输出做审核特别是涉及敏感操作的。第三用沙箱环境执行代码不要让 Agent 直接操作生产系统。LangChain 本身提供了一些安全机制比如allow_dangerous_requests参数但最重要的还是你自己对 Agent 能力的边界有清晰的认识。7.3 关于学习路线如果你刚开始学 LangChain 和 Agent 开发我的建议是先跑通一个最简单的 Agent比如只带一个搜索工具的 Agent。然后逐步加工具加记忆加流程控制。不要一上来就搞多 Agent 协作那样容易迷失在细节里。LangGraph 可以等你有了一定经验之后再学。它的概念不难但需要你对 Agent 的基本执行流程有直观理解否则容易看不懂它在解决什么问题。最后分享一个我常用的调试技巧把 Agent 的verbose日志保存到文件然后用grep搜索关键词。比如你想知道 Agent 有没有调用某个工具直接搜工具名就行。这比在终端里翻滚动日志高效得多。
返回列表