13.如何根据Rannable对象创建工具

以下内容已按 Markdown 格式重新整理并输出:


根据 Runnable 对象创建工具 ------ 完整案例(基于千问 Qwen)

本案例演示如何将一个 LCEL 链 (Runnable 对象)通过 as_tool() 方法封装成一个标准工具,并让 Agent 调用它。

核心知识点:as_tool() 的用法、Runnable 协议、工具 Schema 的自动生成。


1. 环境准备与模型初始化

python 复制代码
# ========== 1. 环境准备与模型初始化 ==========
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langgraph.prebuilt import create_react_agent

load_dotenv()

# 1. 初始化模型(确保 .env 中配置了 DASHSCOPE_API_KEY 和 DASHSCOPE_BASE_URL)
llm = ChatOpenAI(
    model="qwen-plus",
    temperature=0.8,
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url=os.getenv("DASHSCOPE_BASE_URL"),
    model_kwargs={"extra_body": {"enable_thinking": False}},
)

2. 定义核心翻译逻辑为 Runnable(LCEL 链)

python 复制代码
# ========== 2. 定义核心翻译逻辑为Runnable ==========
def build_translator_chain():
    """构建翻译链"""
    prompt = ChatPromptTemplate.from_messages([
        ("system", "你是一个专业的翻译,能将 {source_lang} 翻译成 {target_lang}。只返回翻译结果,不做其他解释。"),
        ("human", "{text}")
    ])
    # 📌 构建LCEL链:提示词 → 模型
    chain = prompt | llm
    return chain

# 创建链
translator_chain = build_translator_chain()

3. 使用 as_tool() 将 Runnable 转换为工具

python 复制代码
# ========== 3. 📌 使用 as_tool() 将Runnable转换为工具 ==========
translation_tool = translator_chain.as_tool(
    name="multi_lang_translator",
    description="""一个多语言翻译工具。接收源语言、目标语言和待翻译文本,
                   并将文本从源语言翻译成目标语言。""",
    # args_schema 可以手动指定,也可以由 LangChain 根据 Runnable 的输入自动推断
    args_schema=None
)

关键点

  • as_tool() 方法接受 namedescription 等参数,生成一个标准的 BaseTool 对象。
  • 工具的参数 Schema 会从 Runnable 的输入类型(通常是函数签名或 Pydantic 模型)自动生成。
  • 此处 translator_chain 是一个 RunnableSequence,其输入类型为 dict,包含 source_langtarget_langtext 三个字段。

4. 创建 Agent 并使用该工具

python 复制代码
# ========== 4. 创建Agent并使用该工具 ==========
tools = [translation_tool]

agent = create_react_agent(
    llm,
    tools=tools,
    prompt="""你是一个智能助手,可以调用工具帮用户解决问题。
              当用户需要翻译时,请直接使用 multi_lang_translator 工具。"""
)

5. 测试运行

python 复制代码
# ========== 5. 测试Agent ==========
if __name__ == "__main__":
    user_input = "我想把这段英文 'LangGraph is great!' 翻译成中文,可以吗?"
    print(f"用户:{user_input}\n")

    final_state = None
    for event in agent.stream(
        {"messages": [HumanMessage(content=user_input)]},
        stream_mode="values"
    ):
        if "messages" in event:
            last_msg = event["messages"][-1]
            last_msg.pretty_print()
            final_state = event

    if final_state and "messages" in final_state:
        final_answer = final_state["messages"][-1].content
        print(f"\n最终答案:{final_answer}")

6. 预期输出示例

复制代码
用户:我想把这段英文 'LangGraph is great!' 翻译成中文,可以吗?

================================ Human Message =================================

我想把这段英文 'LangGraph is great!' 翻译成中文,可以吗?
================================== Ai Message ==================================
Tool Calls:
  multi_lang_translator (call_xxx)
 Call ID: call_xxx
  Args:
    source_lang: 英语
    target_lang: 中文
    text: LangGraph is great!
================================= Tool Message =================================
Name: multi_lang_translator

LangGraph 太棒了!
================================== Ai Message ==================================

已经为您将"LangGraph is great!"翻译成中文:"LangGraph 太棒了!"

最终答案:已经为您将"LangGraph is great!"翻译成中文:"LangGraph 太棒了!"

7. 关键知识点总结

概念 说明
Runnable LangChain 中所有可执行组件的抽象基类(如 Prompt、Model、LCEL 链)
LCEL 链 使用 `
as_tool() 将任何 Runnable 对象转换为一个标准工具(BaseTool),使其能被 Agent 调用
工具 Schema 工具的输入参数结构,可从 Runnable 的输入类型自动推导,也可手动指定
Agent 调用工具 Agent 根据用户输入和工具描述,决定是否调用以及传递何种参数

8. 实践建议

  1. 修改参数 :尝试改变 source_lang / target_lang 的默认值,观察 Agent 是否能正确理解。
  2. 添加新工具 :再封装一个"文本摘要"Runnable,并添加到 tools 列表中。
  3. 深入理解 :打印 translation_tool.args_schema,查看自动生成的 JSON Schema 结构。

如果需要查看某个 Runnable 的输入类型,可以使用:

python 复制代码
print(translator_chain.input_schema.schema())

9.这个链式的写法是不是之前langchain中的,现在怎么在langgraph中也会提到?

LCEL 链式写法确实是 LangChain 的核心特性,而不是 LangGraph 独有的。但两者之间有着紧密的集成关系,所以你在 LangGraph 的资料中会频繁看到 LCEL 的身影。

你观察得很敏锐------LCEL 链式写法确实是 LangChain 的核心特性 ,而不是 LangGraph 独有的。但两者之间有着紧密的集成关系,所以你在 LangGraph 的资料中会频繁看到 LCEL 的身影。

下面详细解释它们的关系以及为什么 LangGraph 中会提到链式写法。


1. LangChain 与 LangGraph 的关系

框架 定位 核心能力
LangChain 通用 LLM 应用开发框架 提供 LCEL、Prompt 模板、输出解析器、文档加载器、向量存储、工具定义等
LangGraph 基于图结构的有状态 Agent 框架 专注于多步骤、可控制、带记忆和循环的复杂工作流(如 ReAct Agent、多 Agent 协作)

LangGraph 构建在 LangChain 之上,复用了 LangChain 的核心组件:

  • 所有 LangChain 的 Runnable 对象(包括 LCEL 链)都可以作为 LangGraph 中的节点(Node)。
  • LangChain 的工具系统(BaseTool)可以直接被 LangGraph 的 ToolNode 使用。
  • LangChain 的消息类型(HumanMessage, AIMessage 等)也是 LangGraph 状态的默认格式。

因此,LCEL 并不是被"移植"到 LangGraph,而是 LangGraph 天然继承了 LangChain 的这一表达方式


2. 为什么在 LangGraph 中会使用 LCEL 链式写法?

在 LangGraph 中,一个图(Graph)的节点通常是一个可调用的函数或一个 Runnable。当节点的逻辑较为复杂(例如包含提示词模板 + LLM 调用 + 输出解析)时,使用 LCEL 可以非常简洁地定义这个节点。

示例:在 LangGraph 节点中使用 LCEL

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from typing import TypedDict

llm = ChatOpenAI(model="gpt-3.5-turbo")

# 定义一个 LCEL 链(Runnable)
summarize_chain = (
    ChatPromptTemplate.from_template("请总结以下内容:\n{content}")
    | llm
)

class State(TypedDict):
    content: str
    summary: str

def summarize_node(state: State):
    # 调用 LCEL 链
    result = summarize_chain.invoke({"content": state["content"]})
    return {"summary": result.content}

builder = StateGraph(State)
builder.add_node("summarize", summarize_node)
builder.set_entry_point("summarize")
builder.add_edge("summarize", END)
app = builder.compile()

好处

  • 代码简洁,无需手动处理提示词拼接和模型调用细节。
  • 可维护性强,LCEL 链可以独立测试和复用。
  • 天然支持流式、批处理和异步操作。

3. LangGraph 的核心贡献不是 LCEL,而是图结构与状态管理

LangGraph 之所以存在,是因为 LCEL(或普通的顺序链)难以优雅地处理需要循环、条件分支、多步骤工具调用、跨步骤持久化状态的场景。LangGraph 提供了:

  • 显式的节点和边
  • 条件路由(add_conditional_edges
  • 共享状态对象(State
  • 持久化和检查点(Checkpointer)

而 LCEL 在其中扮演的是节点内部实现的角色,而不是图编排的角色。


4. 总结:你看到链式写法是正常的

场景 使用的技术
定义单个节点内部逻辑(如调用 LLM、处理输入) LCEL(LangChain)
将多个节点组合成带分支和循环的工作流 LangGraph 的图 API

两者互补而不是替代。LangGraph 不排斥 LCEL,反而鼓励在节点内部使用 LCEL 来保持代码简洁。

所以,你在 LangGraph 教程中看到 LCEL 链式写法,完全符合预期,并且是官方推荐的最佳实践------它体现了 LangChain 生态的统一性和模块化设计。

可以用一个例子让我明白llm 调用的核心逻辑吗?

LLM 调用的核心逻辑 ------ 从裸 API 到框架封装

为了让你彻底理解 LLM 调用的本质,我们用 最原始的方式requests 库)调用一次阿里云百炼的 Qwen 模型,然后逐步解释核心逻辑,再对比 LangChain 的封装做了什么。


1. 最原始的 LLM 调用(无任何框架)

python 复制代码
import requests
import json

# 配置
api_key = "sk-你的阿里云百炼API Key"
url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"

# 构造请求体(messages 是核心)
payload = {
    "model": "qwen-plus",
    "messages": [
        {"role": "system", "content": "你是一个有用的助手。"},
        {"role": "user", "content": "1+1等于几?"}
    ],
    "temperature": 0.7,
    "max_tokens": 100
}

headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

# 发送请求
response = requests.post(url, headers=headers, json=payload)

# 解析响应
if response.status_code == 200:
    result = response.json()
    answer = result["choices"][0]["message"]["content"]
    print(f"模型回答:{answer}")
else:
    print(f"错误:{response.text}")

输出示例

复制代码
模型回答:1+1等于2。

2. 核心逻辑拆解(这是面试可能会问的)

步骤 说明 对应代码
1. 构造请求 组织好模型参数(model)、对话历史(messages)、生成参数(temperature、max_tokens) payload = {...}
2. 设置认证 大多数 API 使用 API Key 通过 Header 认证 headers["Authorization"] = f"Bearer {api_key}"
3. 发送 HTTP 请求 POST 请求到指定的 endpoint requests.post(url, ...)
4. 解析响应 从返回的 JSON 中提取 choices[0].message.content result["choices"][0]["message"]["content"]
5. 错误处理 处理网络错误、限流、认证失败等 if response.status_code != 200

核心本质

LLM 调用就是一个 HTTP 请求,输入是 messages(对话历史),输出是 message.content(模型的回复)。所有框架(LangChain、OpenAI SDK 等)都是对这个过程的封装,让你不用手写 HTTP 细节。


3. 使用 OpenAI SDK(封装了请求/响应细节)

python 复制代码
from openai import OpenAI

client = OpenAI(
    api_key="sk-你的API Key",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

response = client.chat.completions.create(
    model="qwen-plus",
    messages=[
        {"role": "system", "content": "你是一个有用的助手。"},
        {"role": "user", "content": "1+1等于几?"}
    ],
    temperature=0.7,
    max_tokens=100
)

print(response.choices[0].message.content)

相比裸请求的改进

  • 自动处理 HTTP 细节(序列化、反序列化、重试、超时)
  • 强类型响应对象(response.choices[0].message.content
  • 支持流式(stream=True

4. 使用 LangChain(进一步封装成 invoke 模式)

python 复制代码
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    model="qwen-plus",
    api_key="sk-你的API Key",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    temperature=0.7
)

messages = [
    ("system", "你是一个有用的助手。"),
    ("human", "1+1等于几?")
]

response = llm.invoke(messages)
print(response.content)

LangChain 做的额外事情

  • 统一了不同 LLM 提供商的接口(OpenAI、Anthropic、本地模型等),切换模型只需改 modelbase_url
  • 消息格式转换(LangChain 内部使用 HumanMessageSystemMessage 等对象,自动序列化为 API 需要的格式)。
  • 内置工具调用(bind_tools)的支持。
  • 与 LCEL 链无缝集成。

5. 对话记忆的本质(多轮调用)

LLM 本身无状态 ,每次调用都是独立的。要实现多轮对话,需要手动维护消息列表,将历史对话每次都传进去。

python 复制代码
messages = [
    {"role": "system", "content": "你是一个有用的助手。"}
]

while True:
    user_input = input("用户:")
    if user_input.lower() == "quit":
        break
    messages.append({"role": "user", "content": user_input})
    
    # 调用 LLM
    payload["messages"] = messages
    response = requests.post(url, headers=headers, json=payload)
    assistant_reply = response.json()["choices"][0]["message"]["content"]
    
    messages.append({"role": "assistant", "content": assistant_reply})
    print(f"助手:{assistant_reply}")

这就是所有对话 Agent 最基础的实现原理。


6. 核心逻辑总结图

复制代码
┌─────────────────────────────────────────────────────────────┐
│                      LLM 调用核心逻辑                        │
├─────────────────────────────────────────────────────────────┤
│  输入:messages (对话历史) + 参数 (model, temperature...)   │
│                     ↓                                       │
│  ① 构造 HTTP 请求(JSON 格式)                              │
│                     ↓                                       │
│  ② 发送到 API 端点(带认证 Header)                         │
│                     ↓                                       │
│  ③ 接收 JSON 响应                                          │
│                     ↓                                       │
│  ④ 解析 response.choices[0].message.content               │
│                     ↓                                       │
│  输出:模型的回复字符串                                      │
└─────────────────────────────────────────────────────────────┘

7. 面试可能追问的问题

Q:LLM 调用的 token 消耗如何计算?

A:每次请求的输入(messages)和输出(completion)都消耗 token。中文字符通常 1 个 token ≈ 1.5~2 个汉字,不同模型 tokenizer 不同。可以通过响应头或返回的 usage 字段查看。

Q:temperature 和 top_p 的作用?

A:控制随机性。temperature 越低输出越确定(趋向于概率最高的 token),越高越有创造性。top_p 控制采样范围(累积概率阈值)。两者通常不同时调整。

Q:如何处理 API 限流或超时?

A:实现重试机制(指数退避),使用异步请求,或本地缓存重复查询。

Q:LangChain 的 invokestream 有什么区别?

A:invoke 一次性返回完整响应;stream 逐个 token 返回,适合实时显示打字效果。


通过这个例子,你应该理解了:无论框架多么复杂,底层永远是这个简单的 HTTP 请求-响应循环。LangChain、LangGraph 都是在之上构建更高级的抽象(链、图、工具调用等)。

相关推荐
Irissgwe1 天前
十、LangGraph能力详解:工作流的常见模式
python·langchain·ai编程·工作流·langgraph
Irissgwe1 天前
十、LangGraph能力详解:LangGraph 的其他特性
python·ai·langchain·langgraph
张彦峰ZYF2 天前
LangGraph 条件边:让 AI Agent 学会“做选择”
人工智能·大模型·langgraph
装不满的克莱因瓶3 天前
学习 LCEL 表达式:降低 LLM 应用开发难度
人工智能·ai·langchain·agent·智能体·lcel·langgraph
Irissgwe4 天前
【无标题】
python·langchain·ai编程·langgraph
张彦峰ZYF4 天前
深入 LangGraph State:Reducer 是如何让状态“自动合并”的
人工智能·python·大模型·langgraph
Irissgwe5 天前
十、LangGraph能力详解(2)LangGraph入门教程,构建AI工作流
ai·langchain·graph·langgraph
张彦峰ZYF5 天前
LangGraph从零构建生产级 AI Agent 平台的递进式学习项目
人工智能·大模型·langgraph
SuniaWang6 天前
《AgentX 专栏》08-工作流引擎:AgentWorkflow怎么把工具记忆流程串成一条流水线
java·ai·架构·langchain·工作流引擎·langgraph·agent架构