在Chain中,执行流程通常是预先固定的,按照既定步骤依次执行。而Agent的不同之处在于,它不依赖固定流程,而是根据用户输入动态决定下一步行为,并在需要时调用外部工具完成任务。

从能力分工来看,LLM主要负责理解与生成,而Agent是在其外层增加了一层任务决策与执行机制,使系统不仅能回答问题,还能根据任务需求选择合适工具,例如搜索、计算、查询数据库或调用外部接口,并在获取结果后完成整合输出。

因此,Agent的执行过程可以抽象为:

用户输入 → 理解任务 → 判断是否需要工具 → 调用工具 → 获取结果 → 生成回答

对应到LangChain中,一个Agent通常包含以下核心组成部分:

  • 用户输入:任务的起点
  • LLM:负责理解输入并进行决策
  • Prompt Template:组织输入与上下文信息,使模型更容易处理任务
  • Memory:保存历史对话,实现多轮交互
  • Tools:提供外部能力,用于完成模型无法直接处理的任务
  • Planning:在复杂任务中用于拆解步骤并调整执行路径
  • Action:将模型决策转化为具体工具调用
  • 返回结果:整合工具输出与上下文生成最终回答

在更复杂系统中,还可通过多Agent协作实现任务分工。有关Agent的详细介绍见:LangChain最详细教程之Agents

https://dnacap.fund/insights/synergizing-vector-databases-with-langchain

3.4.2 Agent的运行模式与示例

Agent常见的运行方式包括Function Calling和ReAct。相关概念的详细介绍见:Function Call / MCP / ReAct / Skills 如何构成 AI Agent 的完整技术栈从历史的角度梳理Agent-ReAct-Skills-MCP-Tool的关系

Function Calling模式

Function Calling 是一种结构化的工具调用机制。开发者预先定义可调用函数,如天气查询、计算器、数据库检索等。当接收到用户请求时,模型会判断是否需要调用工具;若需要,则只返回函数名和参数,由外部系统执行并将结果返回模型,最终生成完整回复。

这种模式使模型能够获取实时信息并与外部系统交互,因此广泛应用于实时查询、数据处理和业务流程自动化等场景。更多内容可参考:深入解析Function Calling、MCP和Skills的本质差异与最佳实践

ReAct模式

ReAct是Reasoning and Acting的缩写,表示推理与行动交替执行,其核心流程为:

思考 → 行动 → 观察 → 思考 → 行动 → ... → 最终答案

其中,思考用于分析任务与规划步骤,行动用于调用工具,观察用于接收工具返回结果并决定下一步操作。相比Function Calling,ReAct更适合多步任务,例如先检索信息,再进行计算,最后综合生成答案。

https://www.linkedin.com/posts/pavan-belagatti_understand-the-two-popular-ways-ai-agents-activity-7270313617718161408-T6ng

基础Agent示例

以下示例定义了天气查询、计算器和知识搜索三个工具。Agent会根据用户请求自动选择并调用相应工具。例如,当用户询问天气时,Agent 会识别天气查询意图,调用天气工具获取结果,并基于返回信息生成最终回复:

from langchain.agents import create_agent
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """获取城市天气信息"""
    weather_db = {
        "北京": "晴天,15-25度",
        "上海": "多云,18-28度",
        "深圳": "小雨,20-30度",
    }
    return weather_db.get(city, f"{city}的天气信息暂不可用")

@tool
def calculator(expression: str) -> str:
    """执行数学计算"""
    try:
        result = eval(expression)
        return f"计算结果: {expression} = {result}"
    except Exception:
        return "计算错误"

@tool
def search_knowledge(query: str) -> str:
    """搜索知识库"""
    knowledge = {
        "LangChain": "LangChain是一个用于开发LLM应用的框架,支持工具、代理、记忆等功能。",
        "机器学习": "机器学习是AI的一个分支,使系统能够从数据中自动学习和改进。",
    }

    for key, value in knowledge.items():
        if key in query:
            return value

    return f"未找到关于“{query}”的信息"

agent = create_agent(
    model=chat_model,
    tools=[get_weather, calculator, search_knowledge],
    system_prompt="你是一个专业的中文助手,请分析用户问题并选择最合适的工具进行回答。"
)

result = agent.invoke({
    "messages": [
        {"role": "user", "content": "今天上海天气怎么样?"}
    ]
})

final_message = result["messages"][-1]
print(f"回答: {final_message.content}")

带记忆的Agent示例

在多轮对话中,Agent可以通过Checkpointer或Memory保存上下文,从而实现状态保持。

from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()

agent = create_agent(
    model=chat_model,
    tools=[get_weather, calculator, search_knowledge],
    system_prompt="你是一个专业助手,能够记住用户的历史对话。",
    checkpointer=checkpointer
)

config = {"configurable": {"thread_id": "session_001"}}

result1 = agent.invoke(
    {"messages": [{"role": "user", "content": "小明就是我"}]},
    config
)

print("第一轮对话:", result1["messages"][-1].content)

result2 = agent.invoke(
    {"messages": [{"role": "user", "content": "我叫什么名字?"}]},
    config
)

print("第二轮对话:", result2["messages"][-1].content)

通过thread_id标识会话,不同轮次的输入共享同一上下文,从而实现连续对话能力。

4 检索

4.1 Retrieval与RAG

在LangChain中,检索(Retrieval)是连接LLM与外部数据源的核心能力,也是实现RAG的基础。它能够根据用户问题,从文档、数据库或知识库中获取相关信息,并将其作为上下文提供给模型。

如果仅依赖LLM自身进行问答,通常会面临两个问题:一是知识截止,模型的知识停留在训练数据对应的时间点,无法获取最新信息;二是幻觉,模型在缺乏事实依据时,可能生成看似合理但并不准确甚至虚构的内容。在金融、医疗、法律以及企业知识管理等场景中,这些问题尤为突出,因为此类领域对结果的准确性、时效性和可追溯性要求极高。仅依赖模型参数中的知识,不仅难以及时更新,也无法有效说明答案来源,从而影响系统的可信度。

目前尚不存在能够彻底消除幻觉的通用方法,更常见的做法是为模型提供充分、可靠且与问题相关的信息,使生成过程建立在明确依据之上。RAG正是这一思路的典型实践。它在生成答案前先完成检索,将用户问题与外部知识一同提供给模型,使输出基于真实上下文。通过这种方式,既能缓解知识滞后问题,也能降低无依据生成的风险。想要完整学习RAG,可以阅读文章:RAG技术全景解析

典型的RAG流程分为两个阶段:

  1. 第一阶段:入库阶段(离线准备)。系统从PDF、Word、网页、Markdown、数据库等数据源中提取文本,将长文档切分为较小的文本片段,使用Embedding模型将片段转换为向量表示,最后将向量及对应的文本、来源、标题、页码等元数据存入向量数据库,形成可检索的知识库。

  2. 第二阶段:在线阶段(实时推理)。用户提出问题后,系统先将问题转换为向量表示,并在向量数据库中查找语义相似的文本片段,这便是Retrieval的核心。对检索精度要求较高的场景,可在向量召回后引入Rerank模型对候选结果进行重排序,以筛选出更相关的内容。随后,系统将筛选后的文本片段与用户问题组合成Prompt,交由LLM生成最终答案。

在整个过程中,模型能力主要参与三个环节。向量化阶段使用Embedding模型,重排序阶段可按需引入Rerank模型,最终答案生成则由LLM完成。其中,检索、增强和生成构成RAG的核心链路。RAG的实践介绍可参考:Build a RAG agent with LangChain

从工程实践看,RAG无需修改模型参数且接入成本低,知识更新只需维护外部知识库,因此比微调更适用于知识频繁变化、数据私有性强且需保留信息来源的场景。但其效果依赖检索质量,检索失准会影响回答准确性,同时还需维护多个检索组件并受上下文窗口限制。因此,检索质量和上下文利用效率是RAG落地的关键。

https://dev.to/code_2/how-to-create-a-rag-powered-chatbot-with-langchain-and-postgresql-51l9

4.2 Retrieval流程

在基于LLM的知识库问答系统中,Retrieval是连接外部知识与模型生成能力的关键环节。它的核心流程是先获取外部数据,再进行统一加载、清洗切分、向量化和存储,最终形成可供检索的知识库,为后续问答提供上下文支持。更详细介绍见:让Langchain与你的数据对话

典型的Retrieval数据准备流程通常包括五个阶段:Source、Load、Transform、Embed和Store。

  • 阶段一:Source(数据源)
    Source提供原始数据,常见来源包括文本文件、PDF、网页、数据库记录,以及从图片、音频或视频中提取的文本内容。数据源的质量会直接影响后续检索效果,因此在进入处理流程前,需要尽量保证内容完整、来源可靠、格式清晰。

  • 阶段二:Load(加载)
    Load负责将不同来源、不同格式的数据加载为统一结构。在LangChain等框架中,加载后的数据通常会被表示为Document对象。Document一般包含两部分内容:一部分是正文文本,用于后续切分、向量化和检索;另一部分是元数据,用于记录来源、标题、页码、时间等信息,便于结果追溯和引用。

  • 阶段三:Transform(转换)
    Transform负责对Document进行预处理,使其更适合后续向量化和检索。常见操作包括文本分块、噪声清理、内容去重、格式规范化和元数据整理。其中,文本分块是最重要的步骤之一。分块过大可能导致检索结果不够精确,分块过小则可能破坏语义完整性,因此需要根据文档类型和业务场景选择合适的切分策略。

  • 阶段四:Embed(嵌入)
    Embed用于将文本转换为向量表示,以捕获其语义特征。语义越相近的文本,在向量空间中的距离通常越近。检索时,系统会将用户问题和知识库内容分别向量化,并通过相似度计算召回相关文本。常见的相似度计算方法包括余弦相似度、欧氏距离和点积,其中余弦相似度因效果稳定、应用广泛,是文本检索中最常用的方法之一。

  • 阶段五:Store(存储)
    Store负责将文本向量、原始文本和元数据存入向量数据库,并提供高效的相似度检索能力。当用户提出问题时,系统会先将问题转换为向量,再在向量数据库中搜索最相关的文本片段。常见的向量存储方案包括FAISS、Milvus、Chroma等。

经过以上五个阶段,原始数据会被转换为可检索的向量化知识库。后续在线问答时,Retrieval组件便可以根据用户问题从知识库中召回相关内容,并将其作为上下文提供给LLM。相关工具的具体使用方法可参考:LangChain快速入门

1. 数据源
在进入正式流程前,先生成一些日常生活中会用到的文件。以下代码会在当前目录下创建三个文件:一份健身日志(txt)、一份旅行开支记录(csv)和一份读书笔记(json)。后续示例将基于这些文件,依次演示加载、拆分、向量化、存储与检索流程。

import json
import csv

# 1. 健身日志 txt
with open("./fitness_log.txt", "w", encoding="utf-8") as f:
    f.write("周一:跑步30分钟,深蹲4组。\n")
    f.write("周二:瑜伽1小时,拉伸15分钟。\n")
    f.write("周三:休息。\n")
    f.write("周四:HIIT 20分钟,哑铃弯举3组。\n")
    f.write("周五:户外骑行40分钟。\n")

# 2. 旅行开支 csv
with open("./travel_expenses.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["date", "category", "amount", "note"])
    writer.writerow(["2025-05-01", "机票", 1200, "北京-上海"])
    writer.writerow(["2025-05-02", "酒店", 450, "如家精选"])
    writer.writerow(["2025-05-03", "餐饮", 88, "小笼包"])

# 3. 读书笔记 json
notes = [
    {"title": "原子习惯", "author": "詹姆斯·克利尔", "key_point": "微小改变,惊人成果"},
    {"title": "深度工作", "author": "卡尔·纽波特", "key_point": "专注力是稀缺资源"}
]

with open("./reading_notes.json", "w", encoding="utf-8") as f:
    json.dump(notes, f, ensure_ascii=False, indent=2)

2. 文档加载器

文档加载器负责将不同格式的数据读取出来,并转换为统一的Document对象。这样,后续的文本拆分、向量化和检索流程就可以基于统一的数据结构进行处理。

import json
import pandas as pd
from langchain_core.documents import Document

# TXT
txt_docs = [
    Document(
        page_content=open("fitness_log.txt", "r", encoding="utf-8").read()
    )
]

# CSV
df = pd.read_csv("travel_expenses.csv")
csv_docs = [
    Document(page_content=row.to_json(force_ascii=False))
    for _, row in df.iterrows()
]

# JSON
with open("reading_notes.json", "r", encoding="utf-8") as f:
    json_docs = [
        Document(page_content=item["title"])
        for item in json.load(f)
    ]

print("", txt_docs[0].page_content[:50])
print("", csv_docs[0].page_content)
print("", json_docs[0].page_content)

得到文档列表后,通常需要将它们切分成更小的块,以便后续模型处理。

3. 文本拆分器

文本拆分器(Text Splitter)首先将文档拆分为句子等更小的语义单元,再按语义顺序组合成接近预设大小的文本块,并在相邻块之间保留一定重叠,以避免语义在边界处丢失。这样既能控制文本块长度、减少无关信息干扰,又有利于提升检索的精准度和召回效果。

LangChain提供多种拆分策略,常用方法包括按字符或Token长度拆分、递归拆分、按文档结构拆分、语义分块。详情可参考:Chunking Techniques with Langchain and LlamaIndex。以下以递归拆分为例,演示如何将文本切分成指定大小的文档块:

from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter

with open("./fitness_log.txt", "r", encoding="utf-8") as f:
    txt_content = f.read()

txt_docs = [
    Document(page_content=txt_content)
]

splitter = RecursiveCharacterTextSplitter(
    chunk_size=32,
    chunk_overlap=5,
    separators=["\n\n", "\n", "。", ",", " ", ""]
)

txt_chunks = splitter.split_documents(txt_docs)

for i, chunk in enumerate(txt_chunks):
    print(f"块{i}: {chunk.page_content}")

4. 文档嵌入模型

Embedding模型用于将文本映射为固定维度的向量表示,使语义相似的文本在向量空间中的距离更接近,从而支持相似度计算与语义检索。LangChain支持调用多种Embedding模型,例如本地部署的Ollama、开源的Hugging Face,以及OpenAI、DeepSeek等商业API。生成的向量通常需要存储在向量数据库中,以实现高效的语义搜索。下面代码使用Ollama中的Embedding模型,将查询文本转换为向量:

from langchain_ollama import OllamaEmbeddings

embeddings = OllamaEmbeddings(
    model="qwen3-embedding:0.6b",
    base_url="http://127.0.0.1:11434"
)

vec = embeddings.embed_query("跑步30分钟消耗多少热量")

print(f"向量维度: {len(vec)}")
print(f"前5个值: {vec[:5]}")

5. Vector Stores向量存储

向量数据库专门用于存储文档向量并提供高效的相似度搜索,LangChain集成了Chroma、FAISS等多种向量库。下面的例子将前面得到的健身日志文本块向量化后存入Chroma:

# pip install langchain_chroma
from langchain_chroma import Chroma

if len(txt_chunks) == 0:
    print("错误:没有可用的文本块,请检查拆分器配置")
    exit()

print(f"正在将 {len(txt_chunks)} 个文本块向量化并存入Chroma...")

vectorstore = Chroma.from_documents(
    documents=txt_chunks,
    embedding=embeddings,
    persist_directory="./chroma_fitness"
)

print("向量存储创建完成!\n")

执行后,文本块、向量及相关信息会保存到./chroma_fitness目录中。在此基础上,简单场景直接使用基础搜索即可满足需求,但实际应用往往需要更复杂的召回逻辑,例如设置相似度阈值、多路融合等,此时可引入检索器组件来应对。检索库的选择可以见:LangChain Vector Stores

6. 向量检索器

向量存储完成后,文本块、向量和元数据均已保存在向量库中,此时便可基于相似度搜索召回相关内容。检索器在向量库之上封装了统一接口,可配置召回数量和检索方式,便于将结果接入LLM。下面基于Chroma创建检索器,并构建一个简单的问答流程。

from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma

embeddings = OllamaEmbeddings(
    model="qwen3-embedding:0.6b",
    base_url="http://127.0.0.1:11434"
)

vectorstore = Chroma(
    persist_directory="./chroma_fitness",
    embedding_function=embeddings
)

retriever = vectorstore.as_retriever(
    search_kwargs={"k": 5},
    search_type="similarity"
)

prompt = ChatPromptTemplate.from_template(
    "根据以下信息回答问题:\n{context}\n\n问题:{question}"
)

def format_docs(docs):
    return "\n".join([d.page_content for d in docs])

chain = (
    {
        "context": retriever | RunnableLambda(format_docs),
        "question": RunnablePassthrough()
    }
    | prompt
    | chat_model
)

response = chain.invoke("我星期几休息?")
print(response.content)

5 LangChain 1.x总结

LangChain的核心价值并不在于封装模型API,而在于将提示词构建、模型调用、结果解析、RAG、工具调用等能力模块化,并编排成完整的执行流程,使单次模型调用演进为可扩展、可维护的LLM应用。

LLM应用本质上是一个围绕上下文和状态持续演进的执行过程,包括上下文构建、模型推理、工具调用、知识检索和状态更新等环节,并循环运行直至任务完成。LangChain通过Prompt、Runnable、Tool、Retriever、Memory等组件将这些能力解耦,实现更灵活、更易维护的系统架构。

不过,对于单轮问答、轻量级RAG或简单函数调用等场景,直接使用模型SDK通常更加合适。这类场景业务逻辑简单,而主流LLM已经原生支持函数调用、结构化输出和基础Agent能力,引入LangChain往往会增加额外的抽象层和维护成本。

随着LL

落痕的寒假
粉丝 - 57 关注 - 0

+加关注

0

0

升级成为会员

« 上一篇: [深度学习] 大模型学习8下-高性能推理引擎vLLM学习笔记

posted @ 2026-07-01 07:55  落痕的寒假  阅读(160)  评论(0)    收藏  举报

刷新页面返回顶部

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐