搞了两天,我把一个带 MCP 工具的 AI Agent 用 100 行代码跑起来了。

用的 LangGraph 做流程编排,DeepSeek V4 做推理,自定义 MCP Server 给 Agent 提供外部工具能力。效果比我预想的好——Agent 能自己决定什么时候调工具、调哪个工具、怎么处理工具返回的结果。

这篇文章就把核心代码和思路拆开讲清楚。

先说清楚我们要干嘛

目标很简单:搭一个 Agent,能根据用户的问题,自主决定要不要调用外部工具。

比如用户问"帮我查一下北京的天气然后发邮件给老板",Agent 应该能:

  1. 理解意图 → 拆成"查天气"和"发邮件"两步
  2. 调天气工具 → 拿到数据
  3. 调用邮件工具 → 发送结果
  4. 回复用户"搞定了"

不用手写 if-else 判断流程,全靠 LLM 自己推理决策。

先把环境搭好

需要装的东西不多:

pip install langgraph langchain-core langchain-deepseek mcp

主要依赖:

  • LangGraph — 管理 Agent 的状态机和执行流程
  • langchain-deepseek — DeepSeek V4 的 LangChain 集成
  • mcp — MCP 协议的 Python SDK,给 Agent 挂工具用

核心代码:一个能调工具的 Agent

整个 Agent 的核心就三个部分:

1. 定义一个 MCP Tool

from mcp import Tool

def get_weather(city: str) -> str:
    """查天气(模拟)"""
    return f"{city},25°C,晴"

weather_tool = Tool(
    name="get_weather",
    description="查询指定城市的天气",
    input_schema={
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "城市名"}
        },
        "required": ["city"]
    },
    handler=get_weather
)

MCP 的 Tool 定义很干净——一个 name、一个 description、一个 input_schema(JSON Schema 格式)、一个 handler 函数。Agent 看到 description 就知道这个工具能干嘛,看到 input_schema 就知道怎么调。

2. 用 LangGraph 搭 Agent 流程

from langgraph.graph import StateGraph, END
from typing import TypedDict, List, Optional
from langchain_deepseek import ChatDeepSeek

class AgentState(TypedDict):
    messages: List
    tool_calls: Optional[List]

llm = ChatDeepSeek(model="deepseek-v4", temperature=0)

def call_llm(state: AgentState):
    """LLM 决定下一步:直接回答 or 调工具"""
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

def execute_tools(state: AgentState):
    """执行 LLM 要求调用的工具"""
    last_msg = state["messages"][-1]
    results = []
    for tc in last_msg.tool_calls:
        tool = tools[tc["name"]]
        result = tool.handler(**tc["args"])
        results.append(result)
    return {"messages": results, "tool_calls": None}

### 构建图
graph = StateGraph(AgentState)
graph.add_node("llm", call_llm)
graph.add_node("tools", execute_tools)
graph.add_conditional_edges(
    "llm",
    lambda s: "tools" if s.messages[-1].tool_calls else END,
    {"tools": "tools", END: END}
)
graph.add_edge("tools", "llm")
graph.set_entry_point("llm")
agent = graph.compile()

核心逻辑就一个循环:LLM 决定 → 如果要调工具就去执行 → 执行完回来再问 LLM → 直到 LLM 觉得够了直接回答

3. 跑起来

result = agent.invoke({
    "messages": [{"role": "user", "content": "北京的天气怎么样?"}]
})
print(result["messages"][-1].content)

100 行不到,一个能自主调工具的 Agent 就跑起来了。

完整代码

全文加起来 90 多行,核心逻辑就上面那三块。完整代码我放 GitHub 了:[链接]

流程总结:

用户提问 → LLM 推理 → 需要工具吗?
  ├─ 需要 → 执行 MCP Tool → 结果送回 LLM → 再推理
  └─ 不需要 → 直接回答 → 结束

踩坑记录

工具返回太长 LLM 会"忘"

MCP Tool 返回的数据如果太大,LLM 的上下文会被冲淡,导致后续推理质量下降。解决方案:工具只返回摘要,详细信息走引用。

Tool 命名要够直白

MCP 的 Tool discovery 靠的是 description 字段。description 写得模糊,LLM 可能不调或者调错。比如 get_weather 的 description 写"查天气"比"获取气象数据"好得多——LLM 更倾向用简单的词匹配意图。

循环陷阱

如果 LLM 持续判断"需要调工具",会陷入无限循环。要设最大迭代次数:

agent = graph.compile(max_iterations=5)  # 最多调5轮工具

进阶还能怎么玩

  • 多个 Tool 并行调用 — LLM 一次要求调多个工具时,可以并行执行再汇总
  • Tool 之间传数据 — 第一个工具的输出作为第二个工具的输入(MCP 的 context 机制)
  • Human-in-the-loop — 关键操作前让 Agent 先问用户确认

写在最后

100 行代码,一个带 MCP 工具的 AI Agent。MCP 协议让工具接入变得标准化,LangGraph 让流程编排变得声明式——两者搭在一起,效果比我想象的好得多。

下一步我准备往生产环境方向走:加缓存、加错误重试、加日志追踪。有进展再写文章分享。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐