带搜索工具的对话 Agent示例与解析

本示例构建一个可以使用外部工具(Tavily 搜索)的智能对话 Agent,本质上实现了:LLM ↔ 工具 ↔ LLM​的循环,直到模型认为可以直接回答用户为止。代码取自《AI Agent智能体开发实践》第5章。

AI Agent智能体开发实践【行情 报价 价格 评测】-京东

示例代码如下:

复制代码
from typing import Annotated
from typing_extensions import TypedDict

from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

import os
# 设置API密钥(建议通过环境变量配置)
# 初始化Agent
# 1. 基础调用示例
#from langchain_community.chat_models.tongyi import ChatTongyi
#llm = ChatTongyi(model="qwen-turbo", temperature=0)

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
    model="qwen-turbo",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    temperature=0,
)

from langchain_tavily import TavilySearch
import os
# 设置API密钥(建议通过环境变量配置)
os.environ["TAVILY_API_KEY"] = "tvly-dev-iWvU1YGG2SISN1RPOp"
tool = TavilySearch(max_results=2)
tools = [tool]

class State(TypedDict):
    messages: Annotated[list, add_messages]

graph_builder = StateGraph(State)

# Modification: tell the LLM which tools it can call
# highlight-next-line
llm_with_tools = llm.bind_tools(tools)

def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

graph_builder.add_node("chatbot", chatbot)

import json

from langchain_core.messages import ToolMessage


class BasicToolNode:
    """A node that runs the tools requested in the last AIMessage."""

    def __init__(self, tools: list) -> None:
        self.tools_by_name = {tool.name: tool for tool in tools}

    def __call__(self, inputs: dict):
        if messages := inputs.get("messages", []):
            message = messages[-1]
        else:
            raise ValueError("No message found in input")
        outputs = []
        for tool_call in message.tool_calls:
            tool_result = self.tools_by_name[tool_call["name"]].invoke(
                tool_call["args"]
            )
            outputs.append(
                ToolMessage(
                    content=json.dumps(tool_result),
                    name=tool_call["name"],
                    tool_call_id=tool_call["id"],
                )
            )
        return {"messages": outputs}


tool_node = BasicToolNode(tools=[tool])
graph_builder.add_node("tools", tool_node)

def route_tools(
    state: State,
):
    """
    Use in the conditional_edge to route to the ToolNode if the last message
    has tool calls. Otherwise, route to the end.
    """
    if isinstance(state, list):
        ai_message = state[-1]
    elif messages := state.get("messages", []):
        ai_message = messages[-1]
    else:
        raise ValueError(f"No messages found in input state to tool_edge: {state}")
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"
    return END


# The `tools_condition` function returns "tools" if the chatbot asks to use a tool, and "END" if
# it is fine directly responding. This conditional routing defines the main agent loop.
graph_builder.add_conditional_edges(
    "chatbot",
    route_tools,
    # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node
    # It defaults to the identity function, but if you
    # want to use a node named something else apart from "tools",
    # You can update the value of the dictionary to something else
    # e.g., "tools": "my_tools"
    {"tools": "tools", END: END},
)
# Any time a tool is called, we return to the chatbot to decide the next step
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")
graph = graph_builder.compile()

def stream_graph_updates(user_input: str):
    for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
        for value in event.values():
            print("Assistant:", value["messages"][-1].content)

while True:
    try:
        user_input = input("User: ")
        if user_input.lower() in ["quit", "exit", "q"]:
            print("Goodbye!")
            break

        stream_graph_updates(user_input)
    except:
        # fallback if input() is not available
        user_input = "What do you know about LangGraph?"
        print("User: " + user_input)
        stream_graph_updates(user_input)
        break

示例执行结果:

python 复制代码
C:\Users\xiayu\miniconda3\envs\langchain03\python.exe C:\Users\xiayu\PyCharmMiscProject\LangChain官方示例代码学习\Basic\agent7.py 
User: 你是谁
Assistant: 我是通义千问,由阿里巴巴集团旗下的通义实验室研发的超大规模语言模型。我可以帮助你解答问题、创作文字、表达观点、进行对话等。你可以问我任何问题,我会尽力提供帮助。

User: 简述LangGraph开发智能体的方法。
Assistant: LangGraph 是一个用于构建和训练智能体(agents)的框架,它允许开发者通过图结构来定义智能体的行为和决策过程。以下是 LangGraph 开发智能体的主要方法:

1. **定义节点(Nodes)**:
   - 每个节点代表一个特定的任务或功能,例如数据处理、决策制定、执行操作等。
   - 节点可以是简单的函数,也可以是复杂的机器学习模型。

2. **建立边(Edges)**:
   - 边表示节点之间的连接,决定了智能体在不同任务之间的流转。
   - 通过边,智能体可以根据当前状态或输入数据选择下一步要执行的操作。

3. **设计状态(State)**:
   - 状态用于存储智能体在执行过程中需要保持的信息,例如当前的环境信息、历史操作记录等。
   - 状态可以通过图结构传递,确保智能体在不同节点之间保持上下文。

4. **实现逻辑(Logic)**:
   - 每个节点需要实现具体的逻辑,例如处理输入数据、调用外部 API、执行计算等。
   - 逻辑可以是硬编码的,也可以是通过机器学习模型动态生成的。

5. **训练和优化(Training and Optimization)**:
   - 使用强化学习或其他机器学习方法对智能体进行训练,以优化其决策过程。
   - 训练过程中,智能体会根据反馈调整其行为,以更好地完成任务。

6. **集成和部署(Integration and Deployment)**:
   - 将开发好的智能体集成到实际应用中,例如机器人、自动化系统或服务。
   - 部署时需要考虑性能、可靠性和可扩展性。

通过这些步骤,LangGraph 提供了一种灵活且强大的方法来开发和管理智能体,使其能够适应各种复杂的应用场景。
User: 糖醋排骨的步骤
Assistant: 糖醋排骨是一道经典的中式菜肴,以下是制作糖醋排骨的步骤:

### 材料:
- 排骨 500 克
- 生姜 1 小块(切片)
- 大蒜 3 瓣(切末)
- 酱油 2 汤匙
- 醋 2 汤匙
- 糖 2 汤匙
- 料酒 1 汤匙
- 盐 适量
- 水 适量
- 淀粉 1 汤匙(用于勾芡)

### 步骤:

1. **准备排骨**:
   - 将排骨洗净,放入锅中,加入足够的水,煮沸后撇去浮沫。
   - 加入几片生姜和一些料酒,煮约5分钟,捞出排骨,用清水冲洗干净,沥干备用。

2. **煎排骨**:
   - 在锅中加入适量油,加热至六成热,放入排骨,煎至两面金黄,盛出备用。

3. **炒香调料**:
   - 锅中留少量油,加入生姜和大蒜炒香。
   - 加入酱油、醋、糖和料酒,翻炒均匀。

4. **炖煮排骨**:
   - 将煎好的排骨放入锅中,加入适量水(大约能没过排骨)。
   - 大火煮开后转小火慢炖约30分钟,直到排骨变得软烂。

5. **收汁**:
   - 待排骨炖好后,加入适量盐调味。
   - 如果喜欢更浓郁的酱汁,可以用淀粉水勾芡,使汤汁浓稠。

6. **装盘**:
   - 将糖醋排骨盛入盘中,撒上少许葱花或香菜点缀即可。

### 小贴士:
- 煎排骨时要注意火候,避免煎糊。
- 炖煮时间可以根据排骨的大小适当调整。
- 糖醋的比例可以根据个人口味进行调整。

希望你能成功做出美味的糖醋排骨!
User: 

整体执行流程图

START

Chatbot (LLM)

是否调用工具?

├─ 是 → Tools Node → Chatbot

└─ 否 → END

这是一个循环图(Loop Graph)。

代码逐块讲解


1️⃣ 状态定义(State)

复制代码
class State(TypedDict):
    messages: Annotated[list, add_messages]

作用

  • 定义整个图的"全局状态"

  • messages用来存储对话历史

  • add_messages确保新消息被追加,而不是覆盖

📌 所有节点都读写这个 State


2️⃣ 初始化 LLM(通义千问)

复制代码
llm = ChatOpenAI(
    model="qwen-turbo",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    temperature=0,
)

✅ 使用的是 阿里云 DashScope 兼容 OpenAI 接口

temperature=0保证回答稳定


3️⃣ 工具定义(Tavily 搜索)

复制代码
tool = TavilySearch(max_results=2)
tools = [tool]

Tavily:一个为 LLM 优化的实时搜索 API

max_results=2:每次最多返回 2 条搜索结果


4️⃣ 绑定工具给 LLM

复制代码
llm_with_tools = llm.bind_tools(tools)

✅ 这是关键一步

✅ 告诉模型:"你现在可以调用这些工具"

模型输出会变成类似:

复制代码
{
  "tool_calls": [
    {
      "name": "tavily_search",
      "args": {"query": "LangGraph 是什么"}
    }
  ]
}

5️⃣ Chatbot 节点(核心推理)

复制代码
def chatbot(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

✅ 每次调用 LLM:

  • 读取历史消息

  • 决定是否:直接回答或调用工具


6️⃣ 工具执行节点(BasicToolNode)

复制代码
class BasicToolNode:

作用

  • 解析 LLM 的 tool_calls

  • 实际执行工具

  • 把结果封装成 ToolMessage

关键点:

复制代码
tool_result = self.tools_by_name[tool_call["name"]].invoke(
    tool_call["args"]
)

然后返回:

复制代码
ToolMessage(
    content=json.dumps(tool_result),
    name=tool_call["name"],
    tool_call_id=tool_call["id"],
)

📌 这一步让模型知道:"工具返回了什么结果"


7️⃣ 条件路由函数(route_tools)

复制代码
def route_tools(state: State):

✅ 判断是否调用工具:

情况 去向
tool_calls "tools"
没有 END

这是 LangGraph 的 conditional edge


8️⃣ 构建图(Graph)

复制代码
graph_builder = StateGraph(State)

添加节点:

复制代码
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tool_node)
条件边(核心逻辑)
复制代码
graph_builder.add_conditional_edges(
    "chatbot",
    route_tools,
    {"tools": "tools", END: END},
)
工具执行后回到 chatbot
复制代码
graph_builder.add_edge("tools", "chatbot")
启动入口
复制代码
graph_builder.add_edge(START, "chatbot")

9️⃣ 编译并运行图

复制代码
graph = graph_builder.compile()

🔟 流式对话循环

复制代码
def stream_graph_updates(user_input: str):
    for event in graph.stream(...):
        ...

✅ 每轮对话:

  1. 用户输入 → 放入 messages

  2. 图开始执行

  3. 可能多次调用工具

  4. 最终输出 Assistant 回复

相关推荐
武汉唯众智创1 小时前
从0到1搭建AI心理健康预警系统:我是如何用BERT+BiLSTM捕捉情绪拐点的
人工智能·ai大模型·ai心理健康·校园心理健康·ai心理健康预警系统
小仙女的小稀罕1 小时前
适合企业行政开工作会议的,一加会议转任务工具
大数据·人工智能
AI科技星1 小时前
算法联盟·全域数学公理体系下黑洞标量毛发与LVK引力波O4全维理论、求导、证明、计算、验证、分析
人工智能·线性代数·算法·架构·学习方法·量子计算
YuanDaima20481 小时前
图论基础原理与题目说明
数据结构·人工智能·python·算法·图论·手撕代码
code bean1 小时前
【Langchain】 ChatPromptTemplate:从“手动拼字符串“到“专业模板“的进化之路
人工智能·机器学习·langchain
Aipollo1 小时前
AI助手模块工作流程技术总结
人工智能·ai
eastyuxiao1 小时前
主流物联网协议 超详细讲解
大数据·人工智能·物联网·智慧城市·能源·数字孪生
清风lsq1 小时前
大模型-vllm 实现lora解析
人工智能·vllm·大模型推理