使用 Chainlit +langchain+ LangGraph + MCP + Ollama 构建可视化 AI 工具 Agent(完整实战)

前言

随着大模型能力的提升,单纯的对话已经无法满足实际工程需求。越来越多的场景需要模型具备 调用外部工具、访问系统能力、执行复杂任务 的能力。

MCP(Model Context Protocol) 提供了一种统一、标准化的方式,让模型可以通过协议调用外部工具;
LangGraph 则用于构建可控、可追踪的 Agent 推理流程;
Chainlit 提供了一个轻量但功能完整的 Web Chat UI,非常适合 Agent 场景。

本文将介绍如何从零搭建一个:

  • 支持 MCP 多工具调用
  • 支持 Ollama / OpenAI 两种模型后端
  • 支持流式输出
  • 支持 Web UI(Chainlit)
  • 支持多轮对话与状态持久化

的完整 MCP Chat Agent。


效果图

一、整体架构说明

系统整体架构如下:

Chainlit 负责 UI 与用户交互;

LangGraph 负责 Agent 的执行流程与工具调度;

MCP 负责工具协议与进程管理;

Ollama / OpenAI 提供模型推理能力。


二、环境准备

1. 安装 Ollama 并拉取模型

bash 复制代码
ollama pull qwen2.5:7b

确认模型已加载:

bash 复制代码
ollama list

2. 安装 Python 依赖

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

三、MCP 配置说明(mcp.json)

示例 mcp.json

json 复制代码
{
  "mcpServers": {
    "my_tools": {
      "command": "/usr/bin/python3",
      "args": [
        "/home/user/langchain-mcp-demo/pymcp.py"
      ]
    }
  }
}

说明:

  • MCP Server 通过 stdio 启动
  • LangChain 会自动拉起、管理子进程
  • 不需要手动启动 MCP Server

四、核心 Agent 实现(LangGraph + MCP)

1. Agent State 定义

python 复制代码
from typing import Annotated, TypedDict
from langgraph.graph.message import add_messages

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

该 State 用于在 LangGraph 中维护对话消息历史。


2. MCP Agent 实现

python 复制代码
import asyncio
import json

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
python 复制代码
class MCPAgent:
    def __init__(self, backend: str = "ollama"):
        if backend == "ollama":
            self.llm = ChatOllama(
                model="qwen2.5:7b",
                temperature=0.7,
                streaming=True,
            )
        else:
            self.llm = ChatOpenAI(
                model="qwen3-30b-a3b-instruct-2507",
                temperature=0.7,
                api_key="sk-xxxxx",
                base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
                streaming=True,
            )

        with open("mcp.json", "r", encoding="utf-8") as f:
            cfg = json.load(f)

        client = MultiServerMCPClient({
            name: {
                "transport": "stdio",
                "command": s["command"],
                "args": s["args"]
            }
            for name, s in cfg["mcpServers"].items()
        })

        self.tools = asyncio.run(client.get_tools())
        self.llm = self.llm.bind_tools(self.tools)

        self.graph = self._build_graph()

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

    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())

说明:

  • MCP 工具通过 bind_tools 注入模型
  • LangGraph 使用 tools_condition 自动判断是否调用工具
  • MemorySaver 用于多轮对话状态保存

五、Chainlit Web UI 集成

1. 会话启动事件

python 复制代码
import chainlit as cl

@cl.on_chat_start
async def on_start():
    cl.user_session.set("agent", MCPAgent("ollama"))
    cl.user_session.set("thread_id", "default")

    await cl.Message(
        content="MCP Chat Agent 已启动"
    ).send()

2. 消息处理(流式输出 + 工具提示)

python 复制代码
@cl.on_message
async def on_message(message: cl.Message):
    agent = cl.user_session.get("agent")
    thread_id = cl.user_session.get("thread_id")

    msg = cl.Message(content="")
    await msg.send()

    async for event in agent.graph.astream_events(
        {"messages": [("user", message.content)]},
        config={"configurable": {"thread_id": thread_id}},
        version="v2",
    ):
        etype = event["event"]

        if etype == "on_chat_model_stream":
            chunk = event["data"]["chunk"]
            if chunk.content:
                msg.content += chunk.content
                await msg.update()

        elif etype == "on_tool_start":
            await cl.Message(
                content=f"调用工具:{event['name']}",
                author="tool"
            ).send()

        elif etype == "on_tool_end":
            await cl.Message(
                content="工具执行完成",
                author="tool"
            ).send()

3. 完整代码

python 复制代码
import chainlit as cl
import asyncio
import json
import os
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 = "sk-xxxxxxxxxxxxxxxxxxxxx"
OPENAI_API_BASE = "https://dashscope.aliyuncs.com/compatible-mode/v1"
MCP_JSON_PATH = "/home/langchain-mcp-demo/mcp.json"


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


class MCPAgent:
    def __init__(self, backend: str = "ollama"):
        # -------- 模型选择 --------
        if backend == "ollama":
            self.llm = ChatOllama(
                model="qwen2.5:7b",
                temperature=0.7,
                streaming=True,
            )
        else:
            self.llm = ChatOpenAI(
                model="qwen3-30b-a3b-instruct-2507",
                temperature=0.7,
                api_key=OPENAI_API_KEY,
                base_url=OPENAI_API_BASE,
                streaming=True,
            )

        # -------- 加载 MCP --------
        with open(MCP_JSON_PATH, "r", encoding="utf-8") as f:
            mcp_config = json.load(f)

        client_config = {
            name: {
                "transport": "stdio",
                "command": s["command"],
                "args": s["args"],
            }
            for name, s in mcp_config["mcpServers"].items()
        }

        client = MultiServerMCPClient(client_config)
        self.tools = asyncio.run(client.get_tools())

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

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

    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())


# ================== Chainlit 生命周期 ==================

@cl.on_chat_start
async def on_start():
    # 默认用 Ollama,你也可以加 UI 选择
    cl.user_session.set("agent", MCPAgent(backend="ollama"))
    cl.user_session.set("thread_id", "default")

    await cl.Message(
        content="🤖 MCP Chat Agent 已启动(支持工具调用)"
    ).send()


@cl.on_message
async def on_message(message: cl.Message):
    agent: MCPAgent = cl.user_session.get("agent")
    thread_id = cl.user_session.get("thread_id")

    msg = cl.Message(content="")
    await msg.send()

    async for event in agent.graph.astream_events(
        {"messages": [("user", message.content)]},
        config={"configurable": {"thread_id": thread_id}},
        version="v2",
    ):
        etype = event["event"]

        if etype == "on_chat_model_stream":
            chunk = event["data"]["chunk"]
            if chunk.content:
                msg.content += chunk.content
                await msg.update()

        elif etype == "on_tool_start":
            await cl.Message(
                content=f"🛠️ 正在调用工具:**{event['name']}**",
                author="tool"
            ).send()

        elif etype == "on_tool_end":
            await cl.Message(
                content="✅ 工具执行完成",
                author="tool"
            ).send()

六、运行方式

bash 复制代码
chainlit run test.py

浏览器访问:

复制代码
http://localhost:8000

支持功能:

  • 流式模型输出
  • MCP 工具自动调用
  • 工具调用过程可视化
  • 多轮对话上下文保持

参考文章

从零到一:用 Python 构建 MCP Server,基于 LangChain 的 Agent 与工具调用实战

创作不易,记得点赞、收藏、加关注!

相关推荐
李莫若2 小时前
2026权威评测AI学术写作工具全面对比:AIPaperGPT以一站式服务与强保障体系成为全能冠军
人工智能
小郭团队2 小时前
1_5_五段式SVPWM (传统算法反正切+DPWM1)算法理论与 MATLAB 实现详解
人工智能·嵌入式硬件·算法·dsp开发
有Li2 小时前
DACG:用于放射学报告生成的双重注意力和上下文引导模型/文献速递-基于人工智能的医学影像技术
论文阅读·人工智能·文献·医学生
时间会给答案scidag2 小时前
Spring AI Alibaba 学习day01
人工智能·学习·spring
ghie90902 小时前
基于粒子滤波的多目标检测前跟踪(TBD)MATLAB实现
人工智能·目标检测·matlab
分布式存储与RustFS2 小时前
RustFS在AI场景下的实测:从GPU到存储的完整加速方案
开发语言·人工智能·rust·对象存储·企业存储·rustfs·minio国产化替代
Deepoch2 小时前
Deepoc具身模型开发板:半导体制造智能化的技术引擎
人工智能·开发板·半导体·具身模型·deepoc
凤希AI伴侣2 小时前
凤希AI提出FXPA2P:下一代点对点AI服务架构-2026年1月14日
人工智能·架构·凤希ai伴侣
科技与数码3 小时前
中小企业AI知识权威构建:北京鲲鹏伟业的GEO赋能之道——GEO公司助力企业数字化转型
人工智能