模块10: LangChain与CrewAI
使用LangChain和CrewAI加速开发。
为什么使用智能体框架?
从零构建智能体(如我们在模块4-6中所做的)可以给您最大的控制和理解。但随着您构建更多智能体,您会发现自己在重复构建相同的模式:工具注册、记忆管理、提示模板、错误处理和对话循环。框架提供了这些模式的经过实战检验的实现,这样您可以专注于应用逻辑而非基础设施。
Python生态系统中最流行的两个框架是LangChain(通用智能体框架)和CrewAI(多智能体编排框架)。它们服务于不同的需求,甚至可以一起使用。
话虽如此,框架增加了复杂性、抽象层和依赖。并非每个项目都需要框架。关键是理解它们提供什么,以便您可以做出明智的决定。
框架是工具,不是必需品。许多生产AI系统使用直接API调用加自定义代码。框架在以下情况下表现出色:需要快速原型开发、您的用例与其模式紧密匹配,或者您想要访问其集成生态系统。
框架之于智能体开发,就像Web框架(Django、Flask)之于Web开发。您可以从原始socket构建Web服务器,有时也应该这样做。但大多数时候,框架可以在已解决的问题上为您节省数周的工作。
LangChain — 架构
LangChain是最广泛采用的LLM应用框架。它提供模块化架构,每个组件(模型、提示、链、智能体、记忆)可以独立使用或组合在一起。该框架已经显著演进,现在以LangChain表达式语言(LCEL)为中心,用于声明式地组合链。
| 包 | 用途 | 安装 |
|---|---|---|
langchain-core | 基础抽象、LCEL、接口 | 自动安装 |
langchain | 链、智能体、高级API | pip install langchain |
langchain-anthropic | Claude模型集成 | pip install langchain-anthropic |
langchain-openai | OpenAI模型集成 | pip install langchain-openai |
langchain-community | 第三方集成(工具、向量存储) | pip install langchain-community |
langgraph | 有状态的多步骤智能体工作流(图结构) | pip install langgraph |
pip install langchain langchain-anthropic langchain-openai
核心概念
模型
LLM和聊天模型的统一接口。只需修改一行即可在Claude、GPT、Gemini和本地模型之间切换。
提示模板
带变量的结构化模板。支持system/user/assistant消息、少样本示例和动态内容。
输出解析器
将模型输出解析为结构化格式(JSON、Pydantic模型、列表)。处理格式错误输出的重试。
链(LCEL)
使用管道操作符组合组件:prompt | model | parser。声明式、可流式、可批处理。
工具与智能体
将工具定义为Python函数。智能体使用LLM决定调用哪些工具以及调用顺序。
记忆
对话记忆实现:缓冲、摘要、向量支持。可插入任何链。
使用LangChain构建
让我们使用LangChain逐步构建越来越复杂的应用,从简单的链开始,逐步到完整的工具调用智能体。
使用LCEL的简单链
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Create a simple chain: prompt -> model -> parse output
llm = ChatAnthropic(model="claude-sonnet-4-20250514")
prompt = ChatPromptTemplate.from_messages([
("system", "You are an expert {domain} consultant."),
("human", "{question}")
])
parser = StrOutputParser()
# Compose with the pipe operator (LCEL)
chain = prompt | llm | parser
# Invoke
answer = chain.invoke({
"domain": "cybersecurity",
"question": "What are the top 3 threats for small businesses?"
})
print(answer)
使用Pydantic的结构化输出
from pydantic import BaseModel, Field
from langchain_core.output_parsers import JsonOutputParser
class MovieReview(BaseModel):
title: str = Field(description="Movie title")
rating: float = Field(description="Rating out of 10")
summary: str = Field(description="One-sentence summary")
recommend: bool = Field(description="Whether to recommend")
parser = JsonOutputParser(pydantic_object=MovieReview)
prompt = ChatPromptTemplate.from_messages([
("system", "Analyse the movie and respond in JSON.\n{format_instructions}"),
("human", "Review the movie: {movie}")
])
chain = prompt | llm | parser
review = chain.invoke({
"movie": "Inception",
"format_instructions": parser.get_format_instructions()
})
print(f"{review['title']}: {review['rating']}/10 - {review['summary']}")
工具调用智能体
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
import requests
@tool
def search_web(query: str) -> str:
"""Search the web for current information about a topic.
Use this when you need up-to-date information."""
# In production, use a real search API
return f"Top results for '{query}': [simulated search results]"
@tool
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression. Use Python syntax.
Example: '2 ** 10' returns '1024'."""
try:
result = eval(expression, {"__builtins__": {}})
return str(result)
except Exception as e:
return f"Error: {e}"
@tool
def get_weather(city: str) -> str:
"""Get the current weather for a city."""
# Simulated
return f"Weather in {city}: 18C, partly cloudy, humidity 65%"
# Set up the agent
llm = ChatAnthropic(model="claude-sonnet-4-20250514")
tools = [search_web, calculate, get_weather]
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant with access to tools. "
"Use tools when needed to provide accurate answers."),
("human", "{input}"),
("placeholder", "{agent_scratchpad}")
])
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# Run the agent
result = executor.invoke({
"input": "What's 2^16 and what's the weather in Tokyo?"
})
print(result["output"])
在开发期间为AgentExecutor设置 verbose=True。这会打印每次工具调用和中间步骤,便于调试智能体的推理和工具选择。
使用LangChain的RAG链
from langchain_anthropic import ChatAnthropic
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# Set up vector store
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma(persist_directory="./chroma_db",
embedding_function=embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# RAG prompt
prompt = ChatPromptTemplate.from_messages([
("system", """Answer based only on the following context.
If the context doesn't contain the answer, say so.
Context: {context}"""),
("human", "{question}")
])
# Format retrieved docs
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# Compose the RAG chain
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| ChatAnthropic(model="claude-sonnet-4-20250514")
| StrOutputParser()
)
answer = rag_chain.invoke("What is the vacation policy?")
LangChain的抽象可能使调试变得困难。当出现问题时,堆栈跟踪通常会穿过许多层框架代码。始终使用 verbose=True 开始,并考虑使用LangSmith(LangChain的可观测性平台)进行生产监控。
CrewAI — 基于角色的团队
CrewAI采用与LangChain根本不同的方法。不是构建单独的链和智能体,而是定义一个由具有不同角色、目标和背景故事的智能体团队,然后为它们分配任务。CrewAI处理编排 — 决定哪个智能体处理哪个任务,在智能体之间传递结果,并管理整体工作流。
这自然地映射到现实世界的团队动态。就像一个产品团队有研究员、设计师和工程师一样,一个CrewAI团队可以有研究智能体、分析智能体和写作智能体,各自专注于自己的角色。
pip install crewai crewai-tools
如果LangChain像是雇用了一个能做很多事的多面手员工,CrewAI就像是组建了一个项目团队。每个团队成员都有特定的角色、专长和职责。项目经理(CrewAI)协调工作流程,确保任务按正确顺序完成。
CrewAI核心概念
Agent
一个自主单元,具有角色(职位头衔)、目标(试图实现的目标)和背景故事(专长和个性)。
Task
一个具体的任务,带有描述、预期输出格式和指定的智能体。任务可以依赖于其他任务。
Crew
一组智能体及一组任务和流程类型(顺序或层次)。调用 kickoff() 运行整个工作流。
工具
智能体可以使用的函数。CrewAI支持LangChain工具、自定义工具和内置工具(网络搜索、文件I/O等)。
构建Crew
让我们构建一个实用的CrewAI应用:一个分析主题并生成报告的内容研究团队。
from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool # web search tool
# --- Define Agents ---
researcher = Agent(
role="Senior Research Analyst",
goal="Uncover cutting-edge developments and key data points about {topic}",
backstory="""You are an expert research analyst with 15 years of experience.
You excel at finding reliable sources, identifying trends, and
synthesising complex information into clear insights. You always
verify facts from multiple sources.""",
tools=[SerperDevTool()],
verbose=True,
allow_delegation=False
)
analyst = Agent(
role="Data Analyst",
goal="Analyse research findings and identify key patterns and implications",
backstory="""You are a skilled data analyst who specialises in
interpreting qualitative and quantitative information. You create
clear comparisons and identify trends that others miss. You are
known for your balanced, objective analysis.""",
verbose=True,
allow_delegation=False
)
writer = Agent(
role="Technical Writer",
goal="Create a compelling, well-structured report about {topic}",
backstory="""You are an award-winning technical writer who transforms
complex research into accessible, engaging content. You write in a
clear, professional style with strong structure and flow.""",
verbose=True,
allow_delegation=False
)
# --- Define Tasks ---
research_task = Task(
description="""Research the topic: {topic}
Find:
1. Current state and recent developments (last 6 months)
2. Key players and their market positions
3. Technical innovations and breakthroughs
4. Challenges and limitations
5. Market size and growth projections
Compile your findings with sources.""",
agent=researcher,
expected_output="Detailed research notes with sources and key data points"
)
analysis_task = Task(
description="""Analyse the research findings about {topic}.
Produce:
1. SWOT analysis (Strengths, Weaknesses, Opportunities, Threats)
2. Trend analysis with timeline
3. Competitive landscape comparison
4. Risk assessment
5. Three possible future scenarios (optimistic, realistic, pessimistic)""",
agent=analyst,
expected_output="Structured analysis with SWOT, trends, and scenarios",
context=[research_task] # depends on research results
)
report_task = Task(
description="""Write a professional report about {topic}.
The report should include:
- Executive summary (200 words)
- Introduction and background
- Key findings (from research)
- Analysis and implications
- Recommendations
- Conclusion
Target audience: C-level executives.
Tone: Professional but accessible. No jargon without explanation.""",
agent=writer,
expected_output="A polished report in markdown format, 1500-2000 words",
context=[research_task, analysis_task] # depends on both
)
# --- Assemble and Run the Crew ---
crew = Crew(
agents=[researcher, analyst, writer],
tasks=[research_task, analysis_task, report_task],
process=Process.sequential, # tasks run in order
verbose=True
)
# Kick off the crew
result = crew.kickoff(inputs={"topic": "AI agents in enterprise software"})
print(result)
backstory 字段出人意料地重要。它塑造了智能体处理工作的方式 — 提到"注重细节"的背景故事会产生比没有提到的更加细致的输出。把它想象成详细的职位描述加个性档案。
CrewAI中的自定义工具
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
class DatabaseQueryInput(BaseModel):
query: str = Field(description="SQL SELECT query to execute")
class DatabaseQueryTool(BaseTool):
name: str = "query_database"
description: str = "Execute a read-only SQL query against the analytics database."
args_schema: type[BaseModel] = DatabaseQueryInput
def _run(self, query: str) -> str:
import sqlite3
if not query.strip().upper().startswith("SELECT"):
return "Error: Only SELECT queries allowed"
conn = sqlite3.connect("analytics.db")
try:
rows = conn.execute(query).fetchall()
return str(rows[:50]) # limit output
finally:
conn.close()
# Give the tool to an agent
data_agent = Agent(
role="Data Engineer",
goal="Query databases to find requested information",
backstory="Expert in SQL and data analysis.",
tools=[DatabaseQueryTool()]
)
选择正确的方法
没有单一最好的框架。正确的选择取决于您的用例、团队经验和需求。以下是详细的比较,帮助您做出决定。
| 因素 | LangChain | CrewAI | 自定义(直接API) |
|---|---|---|---|
| 最适合 | 通用LLM应用、RAG、单智能体工作流 | 多智能体协作、基于角色的工作流 | 最大控制、简单用例、性能关键场景 |
| 学习曲线 | 中高(大量API接口) | 中低(直观的智能体/任务模型) | 高(需自己构建一切) |
| 抽象级别 | 中(可组合组件) | 高(定义角色和目标,框架处理其余) | 无(您编写每一行) |
| 调试 | 可能困难(深层调用栈) | 中等(verbose模式有帮助) | 容易(您控制一切) |
| 生态系统 | 最大:700+集成 | 增长中:内置工具 + LangChain兼容 | 您自己构建的 |
| 多模型 | 优秀(一行切换模型) | 良好(支持主要提供商) | 您自己实现提供商切换 |
| 生产就绪度 | 高(LangSmith用于监控) | 中等(较新,快速演进) | 取决于您的工程能力 |
从直接API调用开始构建您的第一个智能体。理解每一步发生了什么。然后,如果您发现自己在不同项目中重复构建相同的模式,再采用框架。如果您的用例是单智能体工具调用工作流,LangChain是自然的选择。如果您需要多个智能体协作,CrewAI正是为此而构建的。对于许多生产系统,围绕API的薄自定义封装就是您所需要的全部。
避免在项目早期出现"框架锁定"。LangChain和CrewAI都在快速演进,并伴随着破坏性变更。如果您的整个应用紧密耦合到框架的内部实现,升级将变得痛苦。保持业务逻辑和框架特定代码之间的清晰分离。
决策流程图
- 简单聊天机器人或问答? → 直接API调用或LangChain
- 文档上的RAG? → LangChain(优秀的检索器生态系统)
- 带工具的单智能体? → LangChain或直接API
- 多智能体协作? → CrewAI或LangGraph
- 复杂的有状态工作流? → LangGraph(基于图的智能体编排)
- 需要最大性能和控制? → 直接API调用