乐于分享
好东西不私藏

AI Agent 融合实战:智能数据分析助手——LangChain、Function Calling等的综合应用

AI Agent 融合实战:智能数据分析助手——LangChain、Function Calling等的综合应用

企业AI全栈学习地图 · L3 Agent架构设计 | 构建自主决策的下一代AI助手 · 第三十五篇:# AI Agent 融合实战:智能数据分析助手——LangChain、Function Calling等的综合应用

半夜十一点,老板又在群里 @ 你了。

"昨天哪个产品卖得最好?" "华东区利润为什么下降?" "给我画一张月度销售趋势图,明早周报要用。"

你打开 Excel,写两行 pandas 代码,或者拉个透视表截图、粘贴、发送。一套流程走下来,二十分钟没了。每天都在重复,每天都在重复。

你有没有想过,能不能做一个东西,你只要动嘴,它就把这些活全干了?

这东西现在叫「智能数据分析 Agent」。今天这篇文章,我不讲概念,就带你把一个能自动查数、自动画图、自动分析的 Agent 从零搭出来。而且,我会用两种完全不同的技术栈各搭一遍,让你亲眼看看差别到底在哪。

一套叫 LangChain,你得手动挡——踩离合、换挡、加油门,全自己来。 一套叫 OpenClaw,自动挡——踩油门和刹车,剩下的交给车。

先把丑话说前面:这两种方案没有谁对谁错。手动挡能漂移,自动挡省心。真正的问题从来不是「选哪个」,而是「你什么时候该开哪辆」。

这篇文章,我把它揉碎了掰给你看。

▲ 图1:智能数据分析Agent的三大能力

先别急着敲代码,看清你要造的是个什么东西

大部分教程一上来就甩代码,我不这么干。代码谁都会抄,但「从老板的一句话,拆出机器能执行的步骤」这个能力,才是以后你被 AI 取代不掉的东西。

我们假设手里有一份电商销售数据的 CSV,长这样:

month
region
product
sales
profit
quantity
Jan
East
A
1000
200
100
Jan
West
B
1500
300
150
Feb
East
B
1300
260
130
Mar
South
A
1600
320
160

一共 6 个字段:月份、地区、产品、销售额、利润、销量。9 行数据,覆盖 3 个月、3 个地区、2 种产品。

数据不大,但足够练手。为什么这么设计?因为它同时包含了单条件查询、分组聚合、排序、筛选这些典型操作。你能在这 9 行数据上把 Agent 调通,换成百万行数据,无非就是改个文件路径。

▲ 图2:销售数据CSV的六个字段

现在,老板的问题千奇百怪,但归纳起来就三类:

第一类,数据查询。 "总销售额多少?""华东区总利润多少?""产品 A 在 3 月卖了多少件?" 这类只要精确算个数,不要图,不要长篇分析。

第二类,数据可视化。 "画一张月度销售趋势图""按地区画利润柱状图"。这类要 Agent 会调 matplotlib,还得知道横轴纵轴标题怎么设,最后把图存下来放进周报。

第三类,多步分析。 "为什么华东区利润下降了?" 这是最难的。Agent 得先查利润变化,再查销量变化,再查成本变化,最后综合出结论。一步到位做不到,必须分步推理。

顺一遍你会发现,这三类问题的复杂度是递增的。而你要做的 Agent,三类都得接住。

▲ 图3:老板的三类典型问题

那它到底需要哪些能力?拆开看,五条:

1自然语言理解——老板不会说"对 sales 列执行 max 操作",他说"销售额最高的月份是哪个月"。你得让机器懂「最高」对应 max,「月份」对应 month 列。这不是关键词匹配,是语义理解。
2工具调用——Agent 自己不会算数、不会画图,它得能调外部工具:pandas 算数、matplotlib 画图、seaborn 美化。这就是 Function Calling 的用武之地。
3记忆——"总销售额多少?""那利润呢?" 这个「那」字,靠的就是多轮记忆。没有记忆,Agent 会反问"你说的那指什么"。
4多步推理——"为什么利润下降"这种,得先规划再执行。这就是上节课说的 Plan-and-Execute 模式。
5反思修正——万一算出来利润下降 200%,而财务老师告诉你实际狂赚几千万,Agent 得有自我检查的能力,发现数据读错了、公式用错了,然后修正。

这五条,就是你的「施工图」。有了它,才不至于盖着盖着走样。

▲ 图4:Agent的五大核心能力

方案 A:LangChain,手动挡的乐趣

先说结论:用 LangChain 做这个 Agent,代码量大概 200 行。不多,但每一行都有它的道理。我一段一段带你过。

▲ 图5:LangChain三层架构

环境准备

先开个目录,建虚拟环境,装依赖:

mkdir 5-overallcd 5-overallpython -m venv venvsource venv/bin/activatepip install langchain langchain-openai langgraph pandas matplotlib python-dotenv

Windows 用户把 source venv/bin/activate 换成 venv\Scripts\activate

造数据

建一个 create_data.py

import pandas as pddata = {"month": ["Jan""Jan""Jan""Feb""Feb""Feb""Mar""Mar""Mar"],"region": ["East""West""South""East""West""South""East""West""South"],"product": ["A""B""A""B""A""B""A""B""A"],"sales": [100015001200130014001100200018001600],"profit": [200300240260280220400360320],"quantity": [100150120130140110200180160]}df = pd.DataFrame(data)df.to_csv("sales.csv", index=False)print(df)

跑一下,sales.csv 就生成了。index=False 是不保存行号,避免读回来时多一列没用的。

定义三个工具,这是灵魂

工具是 Agent 的「四肢」。LangChain 里用 @tool 装饰器把普通函数变成工具。关键是那个三引号的文档字符串——它是给模型看的「说明书」。模型靠它判断什么时候该调、参数怎么传。

工具一:查数据

from langchain_core.tools import tool@tooldef query_sales_data(question: str) -> str:"""    对销售数据执行分析查询。支持:    - 最高/最低值:如"销售额最高的月份"    - 总和/平均:如"总销售额"    - 分组统计:如"各地区销售额"    - 条件筛选:如"产品A在3月的销量""""global df    q = question.lower()if"最高"in q or"最大"in q:if"销售额"in q:            max_row = df.loc[df['sales'].idxmax()]returnf"销售额最高的记录是{max_row['month']}月,{max_row['region']}地区,销售额为{max_row['sales']}"elif"利润"in q:            max_row = df.loc[df['profit'].idxmax()]returnf"利润最高的记录是{max_row['month']}月,{max_row['region']}地区,利润为{max_row['profit']}"if"总销售额"in q:returnf"总销售额为{df['sales'].sum()}"if"总利润"in q:returnf"总利润为{df['profit'].sum()}"if"各地区"in q and"销售额"in q:        result = df.groupby('region')['sales'].sum().to_dict()returnf"各地区销售额:{result}"if"产品"in q and"销量"in q:for product in df['product'].unique():if product in q:returnf"{product}产品总销量为{df[df['product']==product]['quantity'].sum()}"returnf"无法理解问题:{question}"

你仔细看,这个工具的「智能」其实很原始——一堆 if "关键词" in q 的字符串匹配。它硬编码了"最高""总销售额""各地区"这些问法。

说实话,这种写法是有硬伤的:换一种问法它就懵,比如"哪个产品卖得最猛"它就接不住,因为代码里写死的是"最高"和"最大"。全局变量 df 也埋了坑。

课程里给了改进方向,我觉得这才是重点:真正生产环境,这种硬编码逻辑应该让大模型自己生成 pandas 代码,或者直接用 NL2SQL 工具,而不是手工写死一堆 if-else。这个认知,比抄代码值钱。

工具二:画图

import matplotlib.pyplot as plt@tooldef plot_sales_data(chart_type: str = "line", metric: str = "sales") -> str:"""    生成销售数据可视化图表。    参数:        chart_type: 'line'折线图 或 'bar'柱状图        metric: 'sales'销售额 或 'profit'利润    返回:        图表保存路径"""global df    plt.figure(figsize=(106))if chart_type == "line":        monthly = df.groupby('month')[metric].sum()        monthly.plot(kind='line', marker='o')        plt.title(f"Monthly {metric.capitalize()} Trend")        plt.xlabel("Month")        plt.ylabel(metric.capitalize())elif chart_type == "bar":        regional = df.groupby('region')[metric].sum()        regional.plot(kind='bar')        plt.title(f"{metric.capitalize()} by Region")    img_path = f"sales_{metric}_{chart_type}.png"    plt.savefig(img_path)    plt.close()returnf"图表已保存为{img_path}"

注意参数设计。chart_type 和 metric 都给了默认值和可选范围,模型看到文档字符串里的 'line'折线图 或 'bar'柱状图,就知道该传什么。参数定义得越清楚,模型调用越准。

工具三:分析趋势

@tooldef analyze_sales_trend(question: str) -> str:"""    对销售数据进行深度分析,生成业务洞察。    参数:        question: 分析问题,如"为什么利润下降了?""""global df    q = question.lower()if"利润下降"in q or"利润为什么"in q:        monthly_profit = df.groupby('month')['profit'].sum()iflen(monthly_profit) >= 2:            months = list(monthly_profit.index)            changes = []for i inrange(1len(months)):                change = monthly_profit.iloc[i] - monthly_profit.iloc[i-1]                changes.append(f"{months[i]}月相比{months[i-1]}月:{change}")returnf"利润变化趋势:\n" + "\n".join(changes)return"请提供更具体的分析问题。"

这个工具干的是环比计算——每个月跟上一个月的利润差值。注意它只算变化量,不做因果分析。真正的原因(销量降了还是成本涨了)还得靠模型结合结果去推断。这个边界很重要,别指望一个工具包打天下。

搭 Agent,装上记忆

工具齐了,现在把它们绑成一个 Agent。这里用 LangGraph 的 StateGraph + checkpointer 来实现记忆:

from langchain_openai import ChatOpenAIfrom langchain.agents import create_agentfrom langgraph.graph import StateGraph, END, add_messagesfrom langgraph.checkpoint.memory import InMemorySaverfrom typing import TypedDict, Annotatedmodel = ChatOpenAI(model="gpt-4", temperature=0)agent = create_agent(    model=model,    tools=[query_sales_data, plot_sales_data, analyze_sales_trend],    system_prompt="""你是一个专业的数据分析助手。你可以:1. 使用 query_sales_data 回答数据查询问题2. 使用 plot_sales_data 生成图表3. 使用 analyze_sales_trend 进行深度分析回答要简洁、专业。""")class AnalyzerState(TypedDict):    messages: Annotated[list, add_messages]    context: dictdef run_agent(state: AnalyzerState):    result = agent.invoke({"messages": state["messages"]})return {"messages": [result["messages"][-1]]}graph = StateGraph(AnalyzerState)graph.add_node("agent", run_agent)graph.set_entry_point("agent")graph.add_edge("agent", END)checkpointer = InMemorySaver()graph = graph.compile(checkpointer=checkpointer)

这段代码里有三个关键点,我单独拎出来,因为这是很多新手卡住的地方:

第一,temperature=0。数据分析要的是稳定可复现的答案,不是天马行空的发挥。设成 0,同一个问题基本给你同一个答案。

第二,add_messages 是个 reducer。它保证每次更新 messages 是「追加」而不是「覆盖」。没有它,对话历史每轮都会被冲掉,记忆就废了。

第三,checkpointer + thread_id 才是记忆的本体。下面交互时,同一个 thread_id 的多次 invoke 会自动加载历史状态。

交互测试

def chat():    thread_id = "data_analyzer_001"    config = {"configurable": {"thread_id": thread_id}}whileTrue:        user_input = input("\n👤 你: ")if user_input.lower() == 'exit':break        result = graph.invoke(            {"messages": [{"role""user""content": user_input}]},            config=config        )print(f"🤖 助手: {result['messages'][-1].content}")if __name__ == "__main__":    chat()

跑起来,试四句:

👤 你: 总销售额是多少?🤖 助手: 总销售额为12900。👤 你: 画一张销售额的月度趋势图🤖 助手: 图表已保存为sales_sales_line.png👤 你: 为什么利润下降了?🤖 助手: ...利润变化趋势:Feb月相比Jan月:20,Mar月相比Feb月:320👤 你: 那利润最高的地区是哪里?🤖 助手: 利润最高的地区是East

看到最后一句了吗?它答出了「East」,靠的是什么?记忆。第三句刚聊过「利润」,第四句一个「那」字,它就知道你在问利润相关的东西。没有 thread_id + checkpointer,它会反问"你说的那指什么"。

这里藏着一个我特别想强调的点:整个过程中,我们从没告诉过 Agent「什么时候该调哪个工具」。它为什么问"总销售额"就自动调 query_sales_data,说"画一张图"就调 plot_sales_data?

答案就俩字:文档字符串。每个 @tool 函数的三引号描述,会被 LangChain 自动转成模型能看到的工具说明。模型读完,再比对用户问题的语义,自己决定调谁、传什么参。

这就是 Function Calling + ReAct 的威力。模型不是走 if-else 的死逻辑,而是在做动态推理。你写的工具描述越清楚,它「猜」得越准。

顺带把 Function Calling 的底层捅一捅

既然这篇文章要「讲透」,那 Function Calling 到底是怎么工作的,我得给你掰开。不然你只会在 LangChain 里用 @tool,换个框架就抓瞎。

说到底,Function Calling 就是一次「两段式」的对话。你在工具函数里写的那个文档字符串,会被框架自动转换成一段结构化的 JSON Schema,塞进发给模型的请求里,告诉它:「我现在手里有这么几个函数,每个函数叫什么、参数是什么、参数类型是什么、干什么用的。」

模型收到请求后,不会自己去算数据——它没这个能力。它做的是「判断」:用户这句话,该不该调工具?调哪个?参数填什么?

如果该调,模型返回的不是一句人话,而是一个结构化的答案,大概长这样:

{"tool""query_sales_data","arguments": {"question""总销售额是多少?"}}

注意,这里返回的是函数名和参数,不是结果。真正去 pandas 里跑 df['sales'].sum() 求和的,是你的 Python 代码,是那个函数本身。模型只是「点了个菜」,菜是你厨房炒的。

所以你会发现一个反常识的点:Function Calling 并不会让模型变得更聪明,它只是给模型装了一个「喊人来帮忙」的开关。 模型的推理能力没变,变的是它能调动外部工具了。锅还是那口锅,只是现在能指挥别人去切菜了。

这也是为什么文档字符串那么重要。模型「点菜」时对不对,全看你对这道菜描述得准不准。你把 query_sales_data 描述成「处理数据」,模型就不知道该在什么场景点它;你描述成「当用户询问总销售额、最高值、分组统计时调用」,它一下就懂了。

很多人卡在「为什么我的 Agent 不调工具」,翻来覆去调模型参数、换提示词,结果根子在工具描述不清晰。这个坑我踩过,写出来就是要你少走弯路。

▲ 图6:Function Calling的两段式原理

进阶:Plan-and-Execute

上面的 Agent 是单节点 ReAct,够用,但碰到"一次性提多个子任务"就吃力了。比如老板说:"帮我分析销售数据,包括总销售额、最高利润月份,再画一张利润趋势图。"

ReAct 模式下,模型会一个个串行执行,但步骤之间没有显式规划。这时候就该上 Plan-and-Execute——先规划,再执行。

核心是自定义 StateGraph,两个节点:

class PlanState(TypedDict):    input: str    plan: list    past_steps: list    final_answer: strdef planner(state: PlanState):"""规划节点:生成执行计划"""    prompt = f"为以下任务生成步骤列表:{state['input']}"    response = model.invoke(prompt)    lines = response.content.strip().split('\n')    plan = [line.strip() for line in lines if line.strip() andnot line.startswith('步骤')]return {"plan": plan}def executor(state: PlanState):"""执行节点:按顺序执行步骤"""ifnot state["plan"]:return {}    step = state["plan"][0]    result = agent.invoke({"messages": [{"role""user""content"f"执行:{step}"}]})    result_content = result["messages"][-1].content    new_past_steps = state.get("past_steps", []) + [(step, result_content)]    remaining_plan = state["plan"][1:]    final_answer = ""ifnot remaining_plan:        final_answer = "\n".join([f"{s}: {r}"for s, r in new_past_steps])return {"plan": remaining_plan, "past_steps": new_past_steps, "final_answer": final_answer}

然后构建图,加条件循环边:

def should_continue(state: PlanState) -> str:return"continue"if state.get("plan"else"end"plan_graph = StateGraph(PlanState)plan_graph.add_node("planner", planner)plan_graph.add_node("executor", executor)plan_graph.set_entry_point("planner")plan_graph.add_edge("planner""executor")plan_graph.add_conditional_edges("executor", should_continue,    {"continue""executor""end": END})plan_app = plan_graph.compile()

planner 负责把一句话拆成步骤列表,executor 每次取第一个步骤执行,should_continue 判断还有没有剩余步骤,有就循环回 executor,没有就结束。

这跟单节点 ReAct 的差别,一句话讲清楚:

ReAct:每步动态决策,模型调用次数多,流程黑盒,灵活。
Plan-and-Execute:预先规划、按步执行,计划列表白盒可见,适合步骤可预见、一次性多任务的场景。

什么时候用 Plan-and-Execute?三个信号:用户一次提多个明确子任务、子任务之间没强依赖、你想控制执行顺序方便调试。

▲ 图7:ReAct与Plan-Execute对比

方案 B:OpenClaw,自动挡的爽

LangChain 那套,200 行代码,看着就累。同样的需求,OpenClaw 怎么搞?

OpenClaw 的核心是「技能」(Skill)。一个技能就是俩文件:SKILL.md 写自然语言描述,__init__.py 写 Python 实现。描述和实现分离,这是它跟 LangChain 最大的哲学差异。

▲ 图8:OpenClaw技能的双文件结构

建技能

进技能目录,建 data_analyzer 文件夹:

mkdir -p ~/.openclaw/skills/data_analyzercd ~/.openclaw/skills/data_analyzertouch SKILL.md __init__.py

写 SKILL.md——用大白话告诉模型这技能能干嘛

---name: data_analyzerdescription: 销售数据分析技能,支持查询、图表生成和深度分析---# data_analyzer 技能## query_sales_data对销售数据执行分析查询。支持:最高销售额/利润、总销售额/总利润、各地区/各月份统计、产品销量筛选。参数: question (string) 用户的分析问题## plot_sales_data生成销售数据可视化图表。参数: chart_type (string) 'line'折线图 或 'bar'柱状图;metric (string) 'sales'销售额 或 'profit'利润## analyze_sales_trend对销售数据进行深度分析,生成业务洞察。参数: question (string) 分析问题

注意,这段全是自然语言,没有一行代码。OpenClaw 运行时会把它注入系统提示,模型读完就知道该调哪个。

这里的好处很实际:产品经理都能写 SKILL.md——用大白话描述一个新技能要干嘛,工程师再去实现对应的方法。角色分工天然就分开了。这在 LangChain 里做不到,因为工具定义和文档字符串绑死在一行代码里。

写 init.py——干活的代码

from openclaw.skill import Skillimport pandas as pdimport matplotlib.pyplot as pltimport osdf = pd.read_csv(os.path.join(os.path.dirname(__file__), "sales.csv"))class DataAnalyzerSkill(Skill):def __init__(self):        super().__init__()self.df = dfdef query_sales_data(self, question: str) -> str:        q = question.lower()if"最高"in q or"最大"in q:if"销售额"in q:                max_row = self.df.loc[self.df['sales'].idxmax()]returnf"销售额最高的记录是{max_row['month']}月,{max_row['region']}地区,销售额为{max_row['sales']}"if"总销售额"in q:returnf"总销售额为{self.df['sales'].sum()}"returnf"无法理解:{question}"def plot_sales_data(self, chart_type: str = "line", metric: str = "sales") -> str:        plt.figure(figsize=(106))if chart_type == "line":self.df.groupby('month')[metric].sum().plot(kind='line', marker='o')elif chart_type == "bar":self.df.groupby('region')[metric].sum().plot(kind='bar')        img_path = f"/tmp/sales_{metric}_{chart_type}.png"        plt.savefig(img_path)        plt.close()returnf"图表已保存为{img_path}"def analyze_sales_trend(self, question: str) -> str:        q = question.lower()if"利润下降"in q or"利润为什么"in q:            monthly_profit = self.df.groupby('month')['profit'].sum()iflen(monthly_profit) >= 2:                months = list(monthly_profit.index)                changes = [f"{months[i]}月相比{months[i-1]}月:{monthly_profit.iloc[i]-monthly_profit.iloc[i-1]}"for i inrange(1len(months))]returnf"利润变化趋势:\n" + "\n".join(changes)return"请提供更具体的分析问题。"skill = DataAnalyzerSkill()

你看,核心逻辑跟 LangChain 那套几乎一模一样——因为干的是同一件事。差别在于:

继承 openclaw.skill.Skill 基类
最后一行 skill = DataAnalyzerSkill(),这是 OpenClaw 的约定,框架会扫到名为 skill 的实例自动加载
没有 StateGraph、没有 checkpointer、没有 thread_id——记忆是内置的,你一行都不用写

配置加载

把 CSV 复制进技能目录,注册,重启:

cp /path/to/sales.csv ~/.openclaw/skills/data_analyzer/openclaw config set skills.load.extraDirs '["~/.openclaw/skills/data_analyzer"]'openclaw gateway restartopenclaw skills list

skills list 显示 ready 就说明加载成功。

测试

打开 OpenClaw(命令行、Web UI 或接入的飞书都行),问:

👤 你: 使用 data_analyzer 技能查询:总销售额是多少?🤖 助手: 总销售额为12900。👤 你: 画一张销售额的月度趋势图🤖 助手: 图表已保存为...👤 你: 那利润最高的地区是哪里?🤖 助手: 利润最高的地区是East

同样,它也没问「你想用什么工具」,而是直接根据 SKILL.md 的描述自己判断调哪个方法。记忆也是开箱即用的——那个「那」字它照样能接住。

OpenClaw 的记忆到底是怎么来的?

LangChain 那边,我得手搓 StateGraph、配 checkpointer、传 thread_id,三件套缺一不可。OpenClaw 这边凭什么啥都不用写?

因为 OpenClaw 把这套东西下沉到了引擎层,变成了框架的默认行为。它内置了 ReAct 循环、记忆管理、多模型路由,并且用一套机制保证它们协同工作。你只需要关注「技能要做什么」,剩下的框架自动给你处理。

具体到记忆:每个会话(session)天然有一个独立的空间,框架会在你每次对话时,自动把历史上下文加载进来、把新的对话追加进去。你不用管 thread_id,不用管状态保存在哪——对使用者来说,记忆就是「隐形的」。

但这里有个代价,你得拎得清:开箱即用,意味着你放弃了控制权。 LangChain 里你能精确控制记忆存哪(内存、数据库)、什么时候清、切哪个会话;OpenClaw 里这些细节被封装起来了,你想改,反而要绕它的规范。

所以这又是一次同一个问题的两面:LangChain 交给你控制权但也把复杂度甩给你;OpenClaw 把复杂度吃掉了,但控制权也随之而去了。没有白瞟的好事,只有合适不合适。

一个很多人忽略的细节:技能目录结构

最后补一个实操细节,很多人卡在这步。一个完整的 OpenClaw 技能,目录长这样:

data_analyzer/├── SKILL.md        # 自然语言描述,给模型看├── __init__.py     # Python 实现,给机器跑└── sales.csv       # 数据文件,跟技能绑在一起

三个东西缺一不可。那个三引号里、__init__.py 最后一行 skill = DataAnalyzerSkill() 更是不能忘——框架就是靠扫描这个名为 skill 的实例来加载技能的。忘了这行,你写的类再漂亮,框架也当它不存在。

这个点,LangChain 用户转过来最容易踩。因为 LangChain 里工具函数就是「函数+装饰器」,没有「实例注册」这一说。转到 OpenClaw,本能地写完类就跑,结果 skills list 里查无此技能,一头雾水。

一对比,差别就出来了

维度
LangChain
OpenClaw
代码量
约 200 行
约 80 行
记忆配置
手动 StateGraph+checkpointer+thread_id
内置,自动
工具调用
依赖 @tool 文档字符串
依赖 SKILL.md 自然语言
调试
LangSmith 能追踪每步
靠日志,较弱
学习曲线
陡,概念多
平缓,一下午上手

八维度看清:这两把刀到底该怎么选

把两种方案从头到尾走完后,我从八个维度做了个系统对比。这是整篇文章最有价值的部分,你留着以后做技术选型用。

1. 工具定义:LangChain 代码即定义——函数签名决定参数,文档字符串是描述。紧凑,但非技术人员插不上手。OpenClaw 把描述(SKILL.md)和实现(init.py)拆开,产品经理写描述、工程师写代码,协作门槛低得多。

2. 记忆管理:LangChain 要你手动搭 StateGraph、加 checkpointer、每次 invoke 传 thread_id。几十行代码,概念多,容易错。OpenClaw 内置记忆,啥都不用做。

3. 流程控制:LangChain 几乎无限灵活——自定义节点、边、条件分支、循环、人机协同,随便定制。OpenClaw 内置的是标准 ReAct 循环,想改流程(比如先规划后执行)就比较费劲。

4. 工具调用机制:LangChain 底层是 Function Calling,模型输出结构化 function_call,精确。OpenClaw 自然语言驱动,模型输出「调用某技能」的文本再解析,更灵活但略不精确。

5. 调试与可观测性:LangChain 有 LangSmith,能看到每一步 Thought/Action/Observation,像看回放。OpenClaw 主要靠日志,没有强可视化工具。

6. 生态共享:LangChain 靠复制代码或 pip 安装,没有统一发现机制。OpenClaw 有 ClawHub,一键发布、一键安装,版本管理、评分、评论都有。

7. 学习曲线:LangChain 概念多——StateGraph、Checkpointer、Reducer,新手容易懵。OpenClaw 掌握 SKILL.md 格式和 Python 类就能上手,一个下午能出东西。

8. 适用场景:企业级复杂应用、需要精细流程控制、多智能体协作的,选 LangChain。快速原型、个人助手、可复用技能的,选 OpenClaw。

▲ 图9:八维度对比总览

我的判断很明确,不跟你和稀泥:这两者根本不是二选一,而是不同阶段用不同的刀。 想快速验证一个想法,OpenClaw 几小时出原型;验证通过了、要上生产扛并发、做精细控制,再用 LangChain 重构。技术是为业务服务的,别把自己焊死在一个框架里。

给你一张选型决策树,照着走就行

光记八个维度还是太散,我给你压缩成一张能直接照做的决策树。遇到一个新需求,问自己三个问题:

第一问:你要的是速度,还是控制?

想在几小时内出个能演示的原型 → OpenClaw。
流程要精确控制、要上生产、要做成复杂系统 → LangChain。

第二问:这个 Agent 需要多复杂的流程?

就是「理解意图 → 调个工具 → 返回答案」这种线性流程 → OpenClaw 的 ReAct 够用。
要先规划再执行、要动态重规划、要人机协同 → LangChain 的 StateGraph,OpenClaw 内置的循环不够你折腾。

第三问:谁来维护、谁来扩展?

团队有产品经理、非技术人员也要参与定义能力 → OpenClaw,SKILL.md 谁都能写。
纯工程团队、追求极致的可调试性和性能 → LangChain,配 LangSmith 看得清清楚楚。

三个问题答完,选型基本就定了。真正的大忌不是「选错了」,而是「想都不想,默认就用自己熟的那一个」。

▲ 图10:技术选型决策树

我见过太多人,因为只会 LangChain,连个查天气的小工具都要上 StateGraph,纯属杀鸡用牛刀;也见过反过来,用 OpenClaw 硬扛一个需要精细状态机的大型系统,最后补丁打得像丐帮的百家衣。

三种混合打法,这才是实战真相

纯选一边的,都是没真干过活的。实际项目里,混合才是常态。三种成熟打法:

打法一:LangChain 做核心,OpenClaw 做外围。 用 LangChain 搭中央决策 Agent,负责复杂流程和多步推理;把查天气、发邮件这些标准化能力封装成 OpenClaw 技能,通过 HTTP 调。核心灵活可控,外围白嫖生态。

打法二:OpenClaw 做原型,LangChain 做生产。 创业项目先拿 OpenClaw 几天搭出能用的 Agent 验证需求,用户量上来后再用 LangChain 重写核心模块,满足高并发、低延迟、定制化要求。这是最常见的路径,因为 OpenClaw 开发速度是 LangChain 的 3-5 倍,但 LangChain 的性能和灵活性更强。先用快的验证,再用强的重构。

打法三:双向集成。 OpenClaw 技能内部通过 HTTP 调用云端 LangChain Agent,反过来 LangChain 也能用 @tool 封装一个 OpenClaw 技能调用。适合大企业内部,不同团队用不同框架但要互相协作。

选哪种,看你的团队、项目阶段、性能要求。核心就一句:别把自己框死。

▲ 图11:三种混合使用策略

复盘:这篇文章真正想让你带走的

写到最后,我想把几件我踩过、也见别人踩过的坑,和一个最重要的认知留给你。

先说三个操作层面的坑:

工具不调对,九成是文档字符串写糊了。别写「处理数据」,要写清楚「当用户询问总销售额时调用」。描述越具体,模型越准。
记忆不生效,检查两处:compile 时有没有传 checkpointer,每次 invoke 有没有传同一个 thread_id。少一个,记忆就断。
回答太啰嗦,去 system_prompt 里加一句「回答要简洁」。别在代码里到处打补丁。

但比起这些细节,我更想让你记住一个认知上的转变。

你看这次整个过程中,最精彩的部分是什么?是我们从没告诉 Agent「什么时候该用哪个工具」,它自己就做对了。靠的是什么?不是魔法,是工具描述的清晰度。

这意味着一个趋势:编码能力会退化,但「把一句模糊的需求,拆成机器能执行的清晰步骤」这个能力,会越来越值钱。

▲ 图12:把需求拆成可执行步骤

未来 AI 时代,谁才是稀缺人才?不是会背 API 的人,而是能把老板那句"华东区利润为什么降了",翻译成"先查利润变化、再查销量变化、再查成本变化、最后综合归因"的人。这才是这个实战案例真正想练你的肌肉。

代码是敲出来的,不是看出来的。这篇文章里的每一段代码,你都得亲手敲一遍、跑一遍、改一遍,它才会长进你身上。别收藏了当吃灰的教程——去建那个目录,去装那个依赖,去问那个 Agent「为什么利润下降了」。

跑通的那一刻,你会发现,跟着做和看着懂,完全是两码事。