从零构建 LangGraph + MCP 的智能对话 Agent:Coze 插件集成实战

摘要

本文将详细介绍如何使用Python构建一个基于LangGraph的MCP Chat Agent,该系统能够集成Coze插件,并支持图像生成功能。我们将探讨其架构设计、核心组件以及实际应用场景。

1. 引言

随着大语言模型(LLM)技术的发展,构建智能对话系统已成为热门话题。本文介绍了一个基于LangGraph的MCP(Model Context Protocol)Chat Agent,它集成了Coze插件系统,能够处理文本对话并支持图像生成与保存功能。

2. 技术栈与依赖

本项目主要使用以下技术:

  • Python 3.x
  • LangChain生态系统(langchain-ollama, langchain-openai)
  • LangGraph用于构建有状态的图结构
  • MultiServerMCPClient用于连接MCP服务
  • Requests库用于HTTP请求处理

3.Linux / mac 配置

bash 复制代码
export COZE_API_KEY=xxxxxxx

4.Windows 配置

Windows 下用下面任意一种方式即可(按你使用的终端选)👇


方法一:PowerShell(最推荐)

powershell 复制代码
$env:COZE_API_KEY="xxxxxxx"

只对当前 PowerShell 窗口 生效

关闭窗口后失效

验证:

powershell 复制代码
echo $env:COZE_API_KEY

方法二:CMD(传统命令行)

cmd 复制代码
set COZE_API_KEY=xxxxxxx

验证:

cmd 复制代码
echo %COZE_API_KEY%

⚠️ 同样只在当前窗口有效


安装所需依赖:

bash 复制代码
pip install langchain-ollama langchain-openai langgraph requests langchain-mcp-adapters

5. 代码解析

5.1 项目结构与配置

python 复制代码
#!/usr/bin/env python3
"""
最终稳定版 MCP Chat Agent
- 自动注册 MCP 工具
- 支持 Ollama / OpenAI
- 支持保存 MCP 生成的图片
"""

import asyncio
import os
import re
import requests
from pathlib import Path
from typing import Annotated, TypedDict

from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient

from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.message import add_messages

# ================== 配置 ==================

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_API_BASE = "https://dashscope.aliyuncs.com/compatible-mode/v1"
COZE_API_KEY = os.getenv("COZE_API_KEY")

在这部分代码中,我们导入了必要的库并定义了API密钥等配置项。值得注意的是,代码使用了阿里云的DashScope兼容接口。

5.2 状态管理

python 复制代码
# ================== LangGraph State ==================

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

这里定义了LangGraph的状态结构,其中messages字段用于存储对话历史,并使用add_messages函数来处理消息列表的合并。

5.3 MCPAgent类详解

5.3.1 类初始化
python 复制代码
# ================== Agent ==================

class MCPAgent:
    def __init__(self, backend: str = "ollama"):
        print("🚀 初始化 MCPAgent", flush=True)
        self.backend = backend

        self.llm = None
        self.tools = None
        self.graph = None

        self.history = []
        self.last_image_url: str | None = None
        self.stream_buffer: str = ""   # ✅ 必须初始化

在初始化方法中,我们设置了代理的基本属性,包括后端类型、语言模型、工具列表、图结构等。特别地,stream_buffer被初始化为处理流式输出中的图像URL。

5.3.2 图像保存功能
python 复制代码
# -------- 保存图片(修复点 1)--------
def save_image(self, url: str, filename: str | None = None):
    Path("images").mkdir(exist_ok=True)

    if not filename:
        filename = url.rstrip("/").split("/")[-1]
        if not filename.endswith(".png"):
            filename += ".png"

    path = Path("images") / filename

    resp = requests.get(url, timeout=30)
    resp.raise_for_status()
    path.write_bytes(resp.content)

    return str(path)

这个方法实现了从URL下载图像并保存到本地的功能。它会自动创建images目录,并根据URL生成合适的文件名。

5.3.3 异步初始化
python 复制代码
# -------- async 初始化 --------
async def async_init(self):
    if self.backend == "ollama":
        self.llm = ChatOllama(model="qwen2.5:7b", temperature=0.7)
        print("🔹 使用 Ollama", flush=True)
    else:
        self.llm = ChatOpenAI(
            model="qwen-vl-max-2025-08-13",
            temperature=0.7,
            api_key=OPENAI_API_KEY,
            base_url=OPENAI_API_BASE,
        )
        print("🔹 使用 OpenAI API", flush=True)

    if not COZE_API_KEY:
        raise RuntimeError("❌ 未设置 COZE_API_KEY")

    print("🔄 连接 Coze MCP", flush=True)

    client = MultiServerMCPClient(
        {  
            "coze_plugin_tuxiangshengcheng_seedream4": {
                "transport": "http",
                "url": "https://mcp.coze.cn/v1/plugins/7548380026094370867",
                "headers": {
                    "Authorization": f"Bearer {COZE_API_KEY}"
                }
            }
        }
    )

    self.tools = await client.get_tools()
    if not self.tools:
        raise RuntimeError("❌ MCP 工具为空")

    print(f"✅ 已注册 {len(self.tools)} 个 MCP 工具", flush=True)

    self.llm = self.llm.bind_tools(self.tools)
    self.graph = self._build_graph()

异步初始化方法根据指定的后端类型(Ollama或OpenAI)配置LLM,然后连接到Coze MCP服务并注册工具。该方法确保了系统在启动时正确配置所有必需的组件。

5.3.4 图结构构建
python 复制代码
# -------- Graph --------
def _build_graph(self):
    g = StateGraph(State)
    g.add_node("agent", self._agent)
    g.add_node("tools", ToolNode(self.tools))
    g.add_edge(START, "agent")
    g.add_conditional_edges("agent", tools_condition)
    g.add_edge("tools", "agent")
    return g.compile(checkpointer=MemorySaver())

这里使用LangGraph构建了一个有状态的图结构,包含两个节点:agent(负责生成回复)和tools(负责执行工具)。条件边允许根据是否需要调用工具来决定下一步的操作。

5.3.5 对话主循环
python 复制代码
# -------- 主循环 --------
async def run(self):
    print("\n🤖 MCP Chat Agent 已启动\n", flush=True)

    while True:
        user = input("你: ").strip()
        if user in ("exit", "quit"):
            break

        if user in ("保存图片", "我要保存图片", "save image"):
            if not self.last_image_url:
                print("❌ 还没有可保存的图片", flush=True)
            else:
                path = self.save_image(self.last_image_url)
                print(f"💾 图片已保存到:{path}", flush=True)
            continue

        self.history.append(("user", user))

        async for event in self.graph.astream_events(
            {"messages": self.history},
            config={"configurable": {"thread_id": "default"}},
            version="v2",
        ):
            if event["event"] == "on_chat_model_stream":
                chunk = event["data"]["chunk"]
                if chunk.content:
                    print(chunk.content, end="", flush=True)

                    self.stream_buffer += chunk.content

                    matches = re.findall(
                        r"!\[\]\((https://s\.coze\.cn/[^)]+)\)",
                        self.stream_buffer,
                    )
                    if matches:
                        self.last_image_url = matches[-1]

            elif event["event"] == "on_tool_start":
                print(f"\n🛠️ 调用工具:{event['name']}", flush=True)

            elif event["event"] == "on_tool_end":
                print("\n✅ 工具执行完成\n", flush=True)
                self.stream_buffer = ""

主循环处理用户输入,检测特定指令(如保存图片),并将用户消息传递给图结构处理。它还监听事件流以捕获工具调用和模型输出,特别是识别图像URL并保存到last_image_url变量中。

5.4 程序入口

python 复制代码
# ================== 入口 ==================

async def main():
    choice = input("选择模型(1=Ollama,2=OpenAI):").strip()
    backend = "ollama" if choice != "2" else "openai"

    agent = MCPAgent(backend)
    await agent.async_init()
    await agent.run()


if __name__ == "__main__":
    asyncio.run(main())

程序入口允许用户选择后端模型,然后初始化并运行代理。

6. 功能特性

6.1 多后端支持

系统支持Ollama和OpenAI两种后端,用户可以根据自己的需求和资源选择合适的模型。

6.2 MCP工具集成

通过MultiServerMCPClient,系统能够自动注册并使用Coze插件提供的工具。

6.3 图像生成与保存

系统可以检测并保存由Coze插件生成的图像,提供了完整的图像处理流程。

6.4 流式输出处理

采用流式输出处理,能够实时显示模型回复,并从中提取图像URL。

5. 使用方法

  1. 设置环境变量:

    bash 复制代码
    export COZE_API_KEY=your_coze_api_key
    export OPENAI_API_KEY=your_openai_api_key  # 如果使用OpenAI后端
  2. 完整代码

python 复制代码
#!/usr/bin/env python3
"""
最终稳定版 MCP Chat Agent
- 自动注册 MCP 工具
- 支持 Ollama / OpenAI
- 支持保存 MCP 生成的图片
"""

import asyncio
import os
import re
import requests
from pathlib import Path
from typing import Annotated, TypedDict

from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient

from langgraph.graph import StateGraph, START
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph.message import add_messages


# ================== 配置 ==================

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "")
OPENAI_API_BASE = "https://dashscope.aliyuncs.com/compatible-mode/v1"
COZE_API_KEY = os.getenv("COZE_API_KEY")


# ================== LangGraph State ==================

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


# ================== Agent ==================

class MCPAgent:
    def __init__(self, backend: str = "ollama"):
        print("🚀 初始化 MCPAgent", flush=True)
        self.backend = backend

        self.llm = None
        self.tools = None
        self.graph = None

        self.history = []
        self.last_image_url: str | None = None
        self.stream_buffer: str = ""   # ✅ 必须初始化

    # -------- 保存图片(修复点 1)--------
    def save_image(self, url: str, filename: str | None = None):
        Path("images").mkdir(exist_ok=True)

        if not filename:
            filename = url.rstrip("/").split("/")[-1]
            if not filename.endswith(".png"):
                filename += ".png"

        path = Path("images") / filename

        resp = requests.get(url, timeout=30)
        resp.raise_for_status()
        path.write_bytes(resp.content)

        return str(path)

    # -------- async 初始化 --------
    async def async_init(self):
        if self.backend == "ollama":
            self.llm = ChatOllama(model="qwen2.5:7b", temperature=0.7)
            print("🔹 使用 Ollama", flush=True)
        else:
            self.llm = ChatOpenAI(
                model="qwen-vl-max-2025-08-13",
                temperature=0.7,
                api_key=OPENAI_API_KEY,
                base_url=OPENAI_API_BASE,
            )
            print("🔹 使用 OpenAI API", flush=True)

        if not COZE_API_KEY:
            raise RuntimeError("❌ 未设置 COZE_API_KEY")

        print("🔄 连接 Coze MCP", flush=True)

        client = MultiServerMCPClient(
            {  
                "coze_plugin_tuxiangshengcheng_seedream4": {
                    "transport": "http",
                    "url": "https://mcp.coze.cn/v1/plugins/7548380026094370867",
                    "headers": {
                        "Authorization": f"Bearer {COZE_API_KEY}"
                    }
                }
            }
        )

        self.tools = await client.get_tools()
        if not self.tools:
            raise RuntimeError("❌ MCP 工具为空")

        print(f"✅ 已注册 {len(self.tools)} 个 MCP 工具", flush=True)

        self.llm = self.llm.bind_tools(self.tools)
        self.graph = self._build_graph()

    # -------- Agent Node --------
    def _agent(self, state: State):
        return {"messages": [self.llm.invoke(state["messages"])]}

    # -------- Graph --------
    def _build_graph(self):
        g = StateGraph(State)
        g.add_node("agent", self._agent)
        g.add_node("tools", ToolNode(self.tools))
        g.add_edge(START, "agent")
        g.add_conditional_edges("agent", tools_condition)
        g.add_edge("tools", "agent")
        return g.compile(checkpointer=MemorySaver())

    # -------- 主循环 --------
    async def run(self):
        print("\n🤖 MCP Chat Agent 已启动\n", flush=True)

        while True:
            user = input("你: ").strip()
            if user in ("exit", "quit"):
                break

            if user in ("保存图片", "我要保存图片", "save image"):
                if not self.last_image_url:
                    print("❌ 还没有可保存的图片", flush=True)
                else:
                    path = self.save_image(self.last_image_url)
                    print(f"💾 图片已保存到:{path}", flush=True)
                continue

            self.history.append(("user", user))

            async for event in self.graph.astream_events(
                {"messages": self.history},
                config={"configurable": {"thread_id": "default"}},
                version="v2",
            ):
                if event["event"] == "on_chat_model_stream":
                    chunk = event["data"]["chunk"]
                    if chunk.content:
                        print(chunk.content, end="", flush=True)

                        self.stream_buffer += chunk.content

                        matches = re.findall(
                            r"!\[\]\((https://s\.coze\.cn/[^)]+)\)",
                            self.stream_buffer,
                        )
                        if matches:
                            self.last_image_url = matches[-1]

                elif event["event"] == "on_tool_start":
                    print(f"\n🛠️ 调用工具:{event['name']}", flush=True)

                elif event["event"] == "on_tool_end":
                    print("\n✅ 工具执行完成\n", flush=True)
                    self.stream_buffer = ""


# ================== 入口 ==================

async def main():
    choice = input("选择模型(1=Ollama,2=OpenAI):").strip()
    backend = "ollama" if choice != "2" else "openai"

    agent = MCPAgent(backend)
    await agent.async_init()
    await agent.run()


if __name__ == "__main__":
    asyncio.run(main())
  1. 运行程序:
bash 复制代码
python 1.py
  1. 选择模型后端,开始对话

  2. 在对话过程中,如果生成了图像,可以输入"保存图片"来保存最新生成的图像

6. 总结

本文介绍了基于LangGraph的MCP Chat Agent的设计与实现。该系统具有良好的架构设计,支持多种后端模型,集成了Coze插件系统,并具备图像生成与保存功能。通过使用LangGraph,我们能够构建一个有状态的、可扩展的对话系统,为后续的功能扩展奠定了基础。

这种架构为构建复杂的AI应用提供了很好的参考,特别是在需要多步骤推理和工具调用的场景下。未来可以进一步扩展更多类型的MCP工具,或增加更丰富的对话管理功能。

相关推荐
linmoo19862 小时前
Langchain4j 系列之二十四 - Scoring (Reranking) Models
人工智能·langchain·langchain4j·scoring·reranking
吾鳴5 小时前
扣子(Coze)实战:20秒一张知识科普卡片,并存储到飞书表格
coze
精致先生5 小时前
LangChain框架
langchain·智能体
组合缺一6 小时前
带来 AI Agent 开发,OpenSolon v3.8.3 发布
java·人工智能·ai·langchain·llm·solon
Zeeland7 小时前
LangChain——如何选择合适的多智能体架构
ai·langchain·openai·ai agent
不正经绣才8 小时前
飞书多维表格工作流指南(AI日报小助手)
ai·飞书·教程·工作流·扣子
ILUUSION_S8 小时前
langchain核心组件Tools
python·langchain
num_killer17 小时前
小白的Langchain学习
java·python·学习·langchain
ba_pi19 小时前
每天写点什么2026-01-13-Langchain-Chatchat及开发环境
jupyter·langchain·ollama