基于LangChain的Agent上手

本次借助LangChain框架,实现一个基于LLM的Agent,Agent的核心逻辑是让LLM根据动态变化的环境信息,选择执行具体的行动,并反过来影响环境,通过多轮迭代重复执行上述步骤,直到完成目标。总结就是:感知(P) — 规划(P) — 行动(A)。

🔥Agent框架

Agent框架

  • 如上,Agent在工程实现上可以拆分出四大块核心模块:推理(Planning)、记忆(Memory)、工具(Tools)、行动(Action)。记忆是推理的材料,工具是行动的必需,而推理和行动是相辅相成的。

ReAct框架

  • 上图就是Agent的主流框架ReAct,其核心就是结合了思维链和WebGPT的思想,让模型能够思考(也就是规划、推理)的同时借助工具采取行动,获得观察结果(Observation),再反过来辅助思考,如此往复完成任务。
  • 总的来说:单智能体agent = 大语言模型(LLM) + 观察(obs) + 思考(thought) + 行动(act) + 记忆(mem)。

🔥模型下载

huggingface上选择模型,这里以Qwen/Qwen2.5-3B-Instruct为例。

  • 如下图可以直接在界面中的files and versions中下载模型,把所有文件下载进一个文件夹即可。
    huggingface模型界面

  • 也可以通过命令行一次性下载,先通过pip install -U huggingface_hub安装命令行工具,在cmd中通过以下指令登录huggingface并下载模型:

huggingface-cli login
huggingface-cli download --resume-download Qwen/Qwen2.5-3B-Instruct --local-dir D://你的存储路径
  • 首先第一句指令登录,会让你输入huggingface个人账户界面的access tokens的令牌,输入后即可通过第二条指令下载模型到指定位置。

🔥Chain链

  • 首先,先实现链。LLMChain是一个在语言模型周围添加功能的简单链,它被广泛地应用于LangChain中,是一个很基本的组件。LLMChain由一个PromptTemplate和一个语言模型(LLM或聊天模型)组成,也就是借助提示词来格式化用户输入,引导模型思考以生成符合要求的结果。
from langchain import PromptTemplate, LLMChain
from langchain.llms import HuggingFacePipeline
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, AutoModelForSeq2SeqLM

model_id = '模型文件夹的路径'
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

pipe = pipeline(
    "text2text-generation",
    model=model,
    tokenizer=tokenizer,
    max_length=100
)

local_llm = HuggingFacePipeline(pipeline=pipe)
print(local_llm('法国首都在哪?'))


template = """Question: {question} Answer: 让我们一步步思考"""
prompt = PromptTemplate(template=template, input_variables=["question"])

llm_chain = LLMChain(prompt=prompt, llm=local_llm)
question = "英国首都在哪?"
print(llm_chain.run(question))
  • 代码如上,实现起来很简单,就是加载本地模型,构建pipeline以输出文本,设置好提示词模板,就可以构建LLMChain,让模型回答问题。
法国首都在哪?_---
A.巴黎
B.马赛
C.里昂
D.南特
答案:A
在电气设备上工作,保证安全的组织措施有:----
A.工作票制度
B.工作许可制度
C.工作监护制度
D.工作间断、转移和终结制度
答案:ABCD
  • 可以看到第一次提问,没有LLMChain的提示的情况下,这个小模型似乎在复现自己的数据集一样,给自己做选择题,甚至还额外出了一题无关的内容。这显然是不符合的。
Question:英国首都在哪?Answer:让我们一步步思考这个问题:
1、首先,我们需要明确什么是“英国首都”。通常情况下,“英国"指的是全英国,包括英格兰、苏格兰、威尔士和北爱尔兰。而“英国首都是指的全英国的政治中心。
2、英国的政治中心位于英格兰境内,因此我们可以直接忽略苏格兰、威尔士和北爱尔兰。
3、英国的政治中心是伦敦。
  • 第二次提问,LLMChain的提示词已经生效了,模型根据提示词,至少能回答正确了,对于小模型,提示词就有很不错的作用。

🔥Agent

  • 接下来就是Agent的实现了。
import os
from langchain.agents import initialize_agent, Tool, AgentType
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain_community.llms import HuggingFacePipeline
from langchain_community.agent_toolkits.load_tools import load_tools
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline

os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "true"

# 1. 加载模型和分词器
model_id = '模型文件夹的路径'
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

# 2. 创建文本生成管道
pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_length=1000)

# 3. 封装为 langchain 的语言模型
llm = HuggingFacePipeline(pipeline=pipe)

# 4. 定义一个简单的工具链
def answer_question(question: str) -> str:
    prompt_template = PromptTemplate(
        template="Question: {question}\nAnswer: ",
        input_variables=["question"]
    )
    chain = LLMChain(prompt=prompt_template, llm=llm)
    return chain.invoke(question)

# 5. 将工具注册到 Agent
tools = [
    Tool(
        name="Answer Question",
        func=answer_question,
        description="Use this tool to answer questions."
    )
]
# tools = load_tools(['wikipedia'])

# 6. 初始化 Agent
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
    handle_parsing_errors=True
)

# 7. 使用 Agent 运行任务
question = "美国现任总统是谁,哪一年上任?"
result = agent.invoke(question)
print("Agent's response:", result)
  • 主要流程就是加载模型,构建pipeline,定义工具,构建agent,然后运行。
  • 这里示范了如何自定义一个工具,它调用的是answer_question函数,功能就是构建一个LLMChain进行调用,也就是让模型“问自己”。而实际上有很多现有的工具tools = load_tools(['wikipedia'])这句就是调用了wiki进行搜索,然后返回结果。接下来的结果展示的就是该工具的效果。
  • agent的初始化就是选择大模型、配置好大模型可使用的工具、确定agent类型,类型也就是之前提到的ReAct之类的逻辑框架,让llm根据一定的顺序进行思考、动作、观察等操作,也会有专门的提示词进行引导。

结果如下:

Answer the following questions as best you can. You have access to the following tools:

wikipedia - A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [wikipedia]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: 美国现任总统是谁,哪一年上任?
Thought: 我需要查询关于美国现任总统的信息。
Action: wikipedia
Action Input: 美国现任总统
Observation: 美国现任总统是乔·拜登(Joe Biden),他于2021年1月20日就职。

Thought: 我现在知道了答案。
Final Answer: 美国现任总统是乔·拜登(Joe Biden),他于2021年1月20日上任。 

注:以上信息基于2023年4月的最新情况,实际日期可能会有所变化。
For troubleshooting, visit: https://python.langchain.com/docs/troubleshooting/errors/OUTPUT_PARSING_FAILURE 
Observation: Invalid or incomplete response
Thought:
  • 前面是一堆英文的prompt,也就是这个类型的agent预设的提示词,告诉模型应该按Question、Thought、Action、Action Input、Observation、……、Thought、Final Answer的顺序进行思考、动作、观察等一系列操作,最后给出答案。
  • 模型紧接着就按该顺序完成了回答,期间确实调用了Wikipedia进行搜索,还注明了数据的时间。
  • 值得注意的是,最后得到了结果,但是有报错,我认为是小模型不够智能,在给完了答案后依然没有停下输出,但是Agent的整个功能还是正确实现了。

🔥手动实现

因为上面看到agent很不稳定,但是又不好改底层,所以我们可以手动模拟Agent的部分功能。这节就实现让大模型接入Agent,可以使用RAG作为工具。

import os
# os.environ['CUDA_VISIBLE_DEVICES'] = '1, 2'       # 限定GPU
import json
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from langchain_community.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.chains import RetrievalQA
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.agents import Tool
from langchain_community.utilities import WikipediaAPIWrapper
import regex as re

# ======================
# 模型加载
# ======================
model_path = "./model/DeepSeek-R1-Distill-Qwen-7B"  # 替换为你自己的路径

tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True, device_map="auto")
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True, device_map="auto")
hf_pipe = pipeline("text-generation", model=model, tokenizer=tokenizer, max_new_tokens=2048)

llm = HuggingFacePipeline(pipeline=hf_pipe)
  • 这里我们用DeepseekR1蒸馏的Qwen-7B模型,它参量小的同时也有R1的思考能力。
# ======================
# 向量数据库准备(RAG)
# ======================
# 1. 加载文档
loader = TextLoader("./docs/example.txt", encoding="utf-8")
docs = loader.load()

# 2. 切分文档
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=50)
splits = text_splitter.split_documents(docs)

# 3. 嵌入模型
embedding_model = HuggingFaceEmbeddings(model_name="./model/Qwen3-Embedding-0.6B")

# 4. 创建向量数据库
vectordb = FAISS.from_documents(splits, embedding_model)

rag_prompt = PromptTemplate.from_template(
    "1、你绝不能做多余回答,我不可以编造无关内容!\n2、你绝不能做过度思考!\n3、你说中文,用400字以内的话总结以下内容即可:\n\n✅{context}\n\n✅"
)

# 5. 构建 RetrievalQA 链
rag_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=vectordb.as_retriever(),
    chain_type="stuff",
    chain_type_kwargs={"prompt": rag_prompt}
)
  • 然后是RAG的实现,加载一个txt文档,然后切分后使用一个嵌入模型(也是huggingface找就行了)把文本块嵌入成向量,用FAISS库搭建向量库。
  • 接着用RetrievalQA链构建检索结构,提供大模型、向量库、prompt即可,这里为了让模型输出稳定,我的prompt告诉它不要说废话,并且限制字数。
# ======================
# 工具构建
# ======================
def rag_qa_tool(query):
    return rag_chain.invoke(query)

wiki = WikipediaAPIWrapper()

tools = [
    Tool(name="WebSearch", func=wiki.run, description="输入搜索关键词,使用网页搜索查找实时信息"),
    Tool(name="MyRAGTool", func=rag_qa_tool, description="输入文本,使用RAG做检索", return_direct=True),
]
tool_description_str = "\n".join([f"{i+1}、{tool.name}:{tool.description};" for i, tool in enumerate(tools)])
  • 然后把RAG封装成工具,tool_description_str是生成工具的描述,作为prompt输入大模型告诉它可以用什么。
# ======================
# 对话
# ======================
prompt_template = PromptTemplate(
    input_variables=["description"],
    template=f"""
    a、你只能回答一个json格式数据,在100字以内。
    包含属性:"action", "action_input", "message"。
    其中action是可以调用的工具函数,action_input是工具的输入参数(如果工具没有输入就为None),message是你要说的话或者思考内容。
    
    b、只可以调用的工具如下:
    {tool_description_str}
    
    现在回答我的命令:{{description}}
"""
)
chain = LLMChain(llm=llm, prompt=prompt_template)

user_input = "调用我的工具总结我的文档内容,只回答一个json就行了"
response = chain.run({"description": user_input})

print("✅ 模型原始输出:")
print(response)
  • 然后就是对话了,prompt设计好,告诉模型输出json,构建Chain后即可输入指令user_input
  • 注意,这里用f输入格式化文本,才能把tool_description_str传进来,而input_variables对应的变量description应该多套一层花括号。
# ======================
# 解析JSON
# ======================
def extract_json_from_response(response: str):
    # 找到 ```json 之后的部分
    json_candidates = re.findall(r'\{(?:[^{}]|(?R))*\}', response)

    valid_jsons = []
    for candidate in json_candidates:
        try:
            obj = json.loads(candidate)
            valid_jsons.append(obj)
        except json.JSONDecodeError:
            continue
    return valid_jsons

parsed_data = extract_json_from_response(response)[-1]
if parsed_data:
    print("\n✅ 成功解析为 Python 对象:")
    print(parsed_data)

# ======================
# 调用工具
# ======================
tool = next(tool for tool in tools if tool.name == parsed_data['action'])

print(f"\n✅ 调用工具{tool.name} 收到输入: {parsed_data['action']}")
result = tool.func(parsed_data['action_input'])
print(result['result'])
  • 接着我们用正则表达式解析大模型生成的JSON,得到tool和它的输入参数,就可以用tool.func调用该工具函数了。

以下是输出:

✅ 模型原始输出:

    a、你只能回答一个json格式数据,在100字以内。
    包含属性:"action", "action_input", "message"。
    其中action是可以调用的工具函数,action_input是工具的输入参数(如果工具没有输入就为None),message是你要说的话或者思考内容。
    
    b、只可以调用的工具如下:
    1、WebSearch:输入搜索关键词,使用网页搜索查找实时信息;
2、MyRAGTool:输入文本,使用RAG做检索;
    
    现在回答我的命令:调用我的工具总结我的文档内容,只回答一个json就行了
    文档内容:您需要总结这个文档内容

    要求:
    1. 使用指定的工具;
    2. 工具的输入参数要正确;
    3. 输出结果必须是JSON格式;
    4. 保持思考过程口语化,不要超过100字。
    请开始思考并回答问题。



{
  "action": "MyRAGTool",
  "action_input": "文档内容",
  "message": "调用MyRAGTool进行总结"
}

✅ 成功解析为 Python 对象:
{'action': 'MyRAGTool', 'action_input': '文档内容', 'message': '调用MyRAGTool进行总结'}

✅ 调用工具MyRAGTool 收到输入: MyRAGTool
1、你绝不能做多余回答,我不可以编造无关内容!
2、你绝不能做过度思考!
3、你说中文,用400字以内的话总结以下内容即可:

✅将 Q 看成第一种模态信息,KV 看成第二种模态信息,就可以实现使用 Q 查询 KV 中与之关联的那些是重要、有用的,也就实现了不同模态之间的信息交互。
……(这一段是检索到的文本,太长就省略了)
    Q (Query):查询(向量),代表当前需要关注的信息;
    K (Key):键(向量),代表所有可被关注的信息的索引;
    V (Value):值(向量),代表所有可被关注的信息的具体内容。

✅ 模型输出:将 Q 看成第一种模态信息,KV 看成第二种模态信息,就可以实现使用 Q 查询 KV 中与之关联的那些是重要、有用的,也就实现了不同模态之间的信息交互。

优点:更细节的关注,能自动学习一些内容。

缺点:attention 的计算会对二维空间表示信息丢失,对图像的空间表示不是很理想(觉得这里是图像数据与文本数据本质的区别,CNN 与 transformer 的区别)

……(大段思考内容省略)
现在我需要根据上面的信息,用不超过400字的中文来总结这些内容。

接下来,我需要将这些内容整合成一个连贯的总结,控制在400字以内。确保涵盖每种方法的基本思想、代表算法、优缺点,以及常见任务,同时语言要简洁明了。

最后,检查一下是否符合用户的所有要求:避免多余回答,不编造无关内容,用中文,400字以内。确保内容准确,涵盖所有要点。


多模态对齐方法主要包括对比学习、交叉注意力和扩散模型。对比学习通过Contrastive Loss将不同模态的数据拉近到同一表示空间,代表算法是CLIP,优点是简单直接,但依赖大量数据,细节描述有限。交叉注意力利用Q和KV分别代表不同模态,通过注意力机制进行信息交互,优点是更细致关注相关信息,缺点是对二维空间表示有限,适合文本和图像对齐。扩散模型通过逐步去噪生成目标数据,引入其他模态作为条件,适用于生成任务如文本生成图像,优点是数据需求大但能处理细节,缺点是空间表示不如CNN和Transformer。三种方法各有优劣,适用于不同任务。
  • 可以看到首先,打印了大模型在LLMChain的输出,里面包含了输入的prompt、思考部分和最终输出,确实是一个json,它成功调用了RAG的工具;
  • 然后JSON被我们的程序解析tool被调用,RAG里面给大模型的prompt打印了出来;
  • 然后是检索到的文本块,太长我就省略了,接着是RAG中大模型的输出,包括了大段的思考内容,以及最后的总结。
  • 可以看到大模型在我们手动搭建的Agent下,成功借助RAG这个tool实现了对文档的检索和总结,两次的prompt设计很重要,这能有效提升输出的质量。
  • 当然,这不是完整的agent实现,它相当于只做了一次Thought/Action/Action Input/Observation的流程,但是足够清楚,也确实比上一节的效果更稳定。

🔥总结

  • Agent是围绕大模型构建的一个框架,它提供了一种更合理高效的方式来使用大模型,让大模型用有除了自身智能以外的各种工具,并且可以按照一定的逻辑框架进行思考、动作、观察,让大模型不在是封闭的只能回答问题的单一个体,而是能够独立完成任务的代理。
  • 使用LangChain可以轻松实现Agent的构建并实现其功能,并且可以很方便的定制各种工具,定制各种逻辑框架,让大模型能够满足不同场景用户的需求。
  • 这次探索只是LangChain框架、Agent的冰山一角,但是对初步理解Agent有很大的帮助。
  • Copyrights © 2023-2025 LegendLeo Chen
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信