LangChain开发Agent智能体(接入阿里云百炼Embedding模型)

创建和运行Agent

单独来说,大语言模型无法采取行动 - 它们只能输出文本。

LangChain 的一个重要用例是创建代理。

代理是使用 LLM 作为推理引擎的系统,用于确定应采取哪些行动以及这些行动的输入应该是什么。

然后可以将这些行动的结果反馈给代理,并确定是否需要更多行动,或者是否可以结束。

我们将构建一个可以与多种不同工具进行交互的代理:一个是本地数据库,另一个是搜索引擎。您将能够向该代理提问,观察它调用工具,并与它进行对话。

下面将介绍使用 LangChain 代理进行构建。LangChain 代理适合入门,但在一定程度之后,我们可能希望拥有它们无法提供的灵活性和控制性。要使用更高级的代理,我们建议查看LangGraph

概念

我们将涵盖的概念包括:

  • 使用语言模型,特别是它们的工具调用能力
  • 创建检索器以向我们的代理公开特定信息
  • 使用搜索工具在线查找信息
  • 聊天历史,允许聊天机器人 "记住" 过去的交互,并在回答后续问题时考虑它们
  • 使用LangSmith调试和跟踪您的应用程序

定义工具

我们首先需要创建我们想要使用的工具。我们将使用两个工具:Tavily(用于在线搜索),然后是我们将创建的本地索引上的检索器。

Tavily

LangChain 中有一个内置工具,可以轻松使用 Tavily 搜索引擎作为工具。

请注意,这需要一个 API 密钥 - 他们有一个免费的层级,但如果您没有或不想创建一个,您可以忽略这一步。

创建 API 密钥后,您需要将其导出为:

export TAVILY_API_KEY="..."

python 复制代码
import os

from langchain_community.tools.tavily_search import TavilySearchResults


def _clean_text(text: str, max_len: int = 16000) -> str:
    one_line = " ".join(text.split())
    if len(one_line) <= max_len:
        return one_line
    return one_line[:max_len] + "..."


search = TavilySearchResults(max_results=2)
results = search.invoke("今天天津天气怎么样?")

for idx, item in enumerate(results, start=1):
    title = item.get("title", "")
    url = item.get("url", "")
    score = item.get("score", "")
    content = _clean_text(item.get("content", ""))

    print(f"[{idx}] {title}")
    print(f"  url: {url}")
    print(f"  score: {score}")
    print(f"  summary: {content}")
    print("-" * 60)

Retriever

Retriever 是 langchain 库中的一个模块,用于检索工具。检索工具的主要用途是从大型文本集合或知识库中找到相关信息。它们通常用于问答系统、对话代理和其他需要从大量文本数据中提取信息的应用程序。

我们还将在自己的一些数据上创建一个Retriever。有关每个步骤的更深入解释,请参阅此教程。

安装阿里云百炼embeddingSDK

shell 复制代码
pip install llama-index-embeddings-dashscope faiss-cpu opencc-python-reimplemented

百炼官网

https://bailian.console.aliyun.com/cn-beijing

配置APIKEY

export DASHSCOPE_API_KEY="..."

搭建网页知识库+语义检索的基础链路

python 复制代码
import os

from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 尽量在导入 loader 前设置,避免出现 USER_AGENT 警告
os.environ.setdefault("USER_AGENT", "my-langchain-retriever/1.0")

def _clean_text(text: str, max_len: int = 7000) -> str:
    one_line = " ".join(text.split())
    if len(one_line) <= max_len:
        return one_line
    return one_line[:max_len] + "..."


def _to_simplified_chinese(text: str) -> str:
    """尝试把繁体转为简体;未安装 opencc 时原样返回。"""
    try:
        from opencc import OpenCC  # type: ignore

        return OpenCC("t2s").convert(text)
    except Exception:
        return text

dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
if not dashscope_api_key:
    raise ValueError(
        "未检测到 DASHSCOPE_API_KEY。请先设置环境变量后再运行,例如:\n"
        "PowerShell: $env:DASHSCOPE_API_KEY='你的key'"
    )

# 定义网页加载器,强制使用 zh-cn 变体页面
loader = WebBaseLoader("https://zh.wikipedia.org/zh-cn/%E7%8C%AB")
# 抓取网页内容,返回Document列表(每个Document有page_content和metadata)
docks = loader.load()
# 把长文本切成小块(chunk)
documents = RecursiveCharacterTextSplitter(
    chunk_size=1000,  # 每块约1000字符
    chunk_overlap=200,  # 相邻块重叠200字符,减少语义断裂
).split_documents(docks)
# 配置embedding模型(阿里云百炼OpenAI兼容接口)
embeddings = OpenAIEmbeddings(
    model="text-embedding-v4",  # 向量模型名
    openai_api_key=dashscope_api_key,  # 你的百炼key
    openai_api_base="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 百炼兼容接口地址
    # 百炼兼容接口要求 input 为字符串或字符串列表,这里禁用 token 数组输入
    check_embedding_ctx_length=False,
    # 百炼单次批量上限为 10
    chunk_size=10,
)
# 把每个文档块转向量,并写入FAILL向量索引(本地内存索引)
vector = FAISS.from_documents(documents, embeddings)
# 把向量库包装成检索器接口,方便query检索
retriever = vector.as_retriever()
# 用问题做语义检索,返回最相关文档快列表,取第一条作为最相关结果
doc = retriever.invoke("猫的特征是什么?")[0]
title = doc.metadata.get("title", "")
source = doc.metadata.get("source", "")
language = doc.metadata.get("language", "")
summary = _clean_text(doc.page_content)
summary = _to_simplified_chinese(summary)

print("=== Retriever Result ===")
print(f"Title   : {title}")
print(f"Source  : {source}")
print(f"Language: {language}")
print("Summary :")
print(summary)

现在,我们已经填充了我们将要进行Retriever的索引,我们可以轻松地将其转换为一个工具(代理程序正确使用所需的格式)。

python 复制代码
from langchain_core.tools import StructuredTool

def search_cat_knowledge(query: str) -> str:
    """从猫的知识库中检索与问题最相关的内容摘要。"""
    doc = retriever.invoke(query)[0]
    title = doc.metadata.get("title", "")
    source = doc.metadata.get("source", "")
    language = doc.metadata.get("language", "")
    summary = _to_simplified_chinese(_clean_text(doc.page_content, max_len=1000))
    return (
        f"Title: {title}\n"
        f"Source: {source}\n"
        f"Language: {language}\n"
        f"Summary: {summary}"
    )


# 这是"retriever 封装成 tool"的可用写法
retriever_tool = StructuredTool.from_function(
    func=search_cat_knowledge,
    name="cat_knowledge_retriever",
    description="检索猫相关知识,输入一个问题字符串并返回摘要。",
    return_direct=True,
)

print("\n=== Retriever Tool Result ===")
print(retriever_tool.invoke({"query": "猫的听觉有什么特点?"}))

工具

既然我们都创建好了,我们可以创建一个工具列表,以便在下游使用。

tools = [search, retriever_tool]

使用语言模型

接下来,让我们学习如何使用语言模型来调用工具。LangChain支持许多可以互换使用的不同语言模型 - 选择您想要使用的语言模型!

您可以传入消息列表来调用语言模型。默认情况下,响应是一个content字符串。

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import HumanMessage

model = ChatDeepSeek(model="deepseek-chat", temperature=0)

response = model.invoke([HumanMessage(content="hi!")])
print(response.content)

现在,我们可以看看如何使这个模型能够调用工具。为了使其具备这种能力,我们使用 .bind_tools 来让语言模型了解这些工具。

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import HumanMessage

from tools_tavily import search
from tools_retriever import retriever_tool

model = ChatDeepSeek(model="deepseek-chat", temperature=0)
tools = [search, retriever_tool]
model_with_tools = model.bind_tools(tools)

现在我们可以调用模型了。让我们首先用一个普通的消息来调用它,看看它的响应。我们可以查看content字段和tool_calls字段。

python 复制代码
response = model_with_tools.invoke([HumanMessage(content="你好")])
print("ContentString: ", response.content)
print("ToolCalls:", response.tool_calls)

输出:

text 复制代码
ContentString:  你好!很高兴见到你!😊

有什么我可以帮你的吗?无论是想了解猫咪的知识、查询最新信息,还是随便聊聊天,我都很乐意陪你!
ToolCalls: []

现在,让我们尝试使用一些期望调用工具的输入来调用它。

python 复制代码
response = model_with_tools.invoke([HumanMessage(content="今天天津天气怎么样?")])
print("ContentString: ", response.content)
print("ToolCalls:", response.tool_calls)

输出:

text 复制代码
ContentString:  我来帮你查一下今天天津的天气情况。
ToolCalls: [{'name': 'tavily_search_results_json', 'args': {'query': '天津 今天 天气 2025年'}, 'id': 'call_00_ZpYVSf5DPTDFHfiaPx7y4864', 'type': 'tool_call'}]

我们可以看到现在没有内容,但有一个工具调用!它要求我们调用Tavily Search工具。

这并不是在调用该工具 - 它只是告诉我们要调用。为了实际调用它,我们将创建我们的代理程序。

创建代理程序

既然我们已经定义了工具和LLM,我们可以创建代理程序。我们将使用一个工具调用代理程序。

现在,我们可以使用LLM、提示和工具初始化代理。代理负责接收输入并决定采取什么行动。关键的是,代理不执行这些操作 - 这是由AgentExecutor(下一步)完成的。

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent

from tools_tavily import search
from tools_retriever import retriever_tool

model = ChatDeepSeek(model="deepseek-chat", temperature=0)
tools = [search, retriever_tool]
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个有帮助的助手,请优先使用工具回答事实类问题。"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),  # agent执行过程中需要的指令
    ]
)

# tools: 给Agent规划层用的,让大模型知道有哪些工具可以调用,每个工具的参数是什么
# 从而决定要不要调用工具,调用哪个工具
# 声明可选工具
agent = create_tool_calling_agent(model, tools, prompt)
# tools: 给Agent执行层用户,当Agent产出tool call后
# Executor需要拿到真实的工具对象去执行,并管理中间步骤
# 注册可执行工具
# verbose=True: 打印Agent的运行过程,进入/结束chain、模型的中间推理步骤、调用了哪个工具/工具入参是什么、工具返回了什么
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

chat_history = [
    HumanMessage(content="你好,我想了解猫。"),
    AIMessage(content="好的,请问你想了解猫的哪些方面?"),
]

result = agent_executor.invoke({"input": "猫的特征是什么?今天天津天气怎么样?", "chat_history": chat_history})
print(result["output"])

大模型来分析调用哪个工具,真正调用工具的执行者是Agent,Agent会根据输入将Task任务做拆分。

添加记忆

如前所述,此代理是无状态的。这意味着它不会记住先前的交互。要给它记忆,我们需要传递先前的 chat_history。注意:由于我们使用的提示,它需要被称为chat_history。如果我们使用不同的提示,我们可以更改变量名

python 复制代码
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

chat_history = [
    HumanMessage(content="你好,我的名字是Jack。"),
    AIMessage(content="好的,Jack你好。"),
]

result = agent_executor.invoke({"input": "我叫啥?", "chat_history": chat_history})
print(result["output"])

如果我们想要自动跟踪这些消息,我们可以将其包装在一个RunnableWithMessageHistory中

python 复制代码
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_classic.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory

from tools_tavily import search
from tools_retriever import retriever_tool


store = {}


def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = ChatMessageHistory()
    return store[session_id]


model = ChatDeepSeek(model="deepseek-chat", temperature=0)
tools = [search, retriever_tool]
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个有帮助的助手,请优先使用工具回答事实类问题。"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),  # agent执行过程中需要的指令
    ]
)

# tools: 给Agent规划层用的,让大模型知道有哪些工具可以调用,每个工具的参数是什么
# 从而决定要不要调用工具,调用哪个工具
# 声明可选工具
agent = create_tool_calling_agent(model, tools, prompt)
# tools: 给Agent执行层用户,当Agent产出tool call后
# Executor需要拿到真实的工具对象去执行,并管理中间步骤
# 注册可执行工具
# verbose=True: 打印Agent的运行过程,进入/结束chain、模型的中间推理步骤、调用了哪个工具/工具入参是什么、工具返回了什么
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)


def invoke_with_session(session_id: str, user_input: str) -> str:
    history = get_session_history(session_id)
    response = agent_executor.invoke(
        {
            "input": user_input,
            "chat_history": history.messages,
        }
    )
    output = response["output"]
    history.add_user_message(user_input)
    history.add_ai_message(output)
    return output


response = invoke_with_session("123", "Hi,我的名字是Maya")
print(response)

response = invoke_with_session("123", "我叫啥?")
print(response)

Agent的核心是由几个组成驱动的:

  • 工具:大模型匹配对应的工具、Agent负责调用
  • 提示词:先做任务规划、然后拆分成多个子任务
相关推荐
染指11102 小时前
14.LangChain框架5-文档切分
数据库·人工智能·ai·langchain
兆。3 小时前
如何在本地搭建天气智能体项目
langchain·openai·qwen·ollama·本地大模型
贺国亚3 小时前
Agent框架-LangChain-LangGraph与AutoGen
langchain
小何code12 小时前
人工智能【第52篇】RAG系统实战:检索增强生成技术详解
embedding·向量数据库·rag·检索增强生成·llm应用
程序员Aries13 小时前
LangChain 与大语言模型
人工智能·语言模型·langchain
swipe18 小时前
DeepAgents 多 Agent 深度调研助手工程实战:从 createDeepAgent 到可控调研工作流
javascript·面试·langchain
格桑阿sir19 小时前
10-大模型智能体开发工程师:RAG检索增强生成
ai·大模型·llm·embedding·agent·检索增强·rag
寻道码路1 天前
LangChain4j Java AI 应用开发实战(十):Embedding 模型与文本分类 - 语义向量化
java·人工智能·ai·embedding
贺国亚1 天前
RAG检索增强-向量库与Chunking
prompt·embedding