【Langchain】超详细构建Langchain架构下的MCP与Agent智能结合体

在实现causalchat功能的时候,遇到了需要反复调用不同的工具的场景,如果只有一个mcp服务,那么也许不需要agent智能体的构建,但是如果一旦加入rag等复杂功能的实现,谁先谁后就不能在代码中进行硬编码了,为此我们将实现一个agent架构 相关文章: 【MCP】小白详解从0开始构建MCP服务端全流程 【RAG+向量数据库】小白从0构建一个rag和向量数据库demo

1. MCP工具的参数封装

前提:对于Langchain中的agent模式,需要一个严格的接口定义,而在 LangChain 中,这个标准接口就是 BaseTool 类。 对于所有工具,agent都需要有以下几个参数

  • 它必须是一个 BaseTool 对象:这是最基本的要求。
  • 必须有 name 和 description:Agent 通过这些信息来"思考"和决定用哪个工具。
  • 必须有 args_schema:这是一个 Pydantic 模型,它精确地告诉 Agent 这个工具需要哪些参数,以及这些参数的类型。这是 Agent 能-够正确生成调用参数的基础。
  • 必须有 _arun 方法:这是 Agent 按下工具"启动按钮"的地方。当 Agent 决定使用某个工具时,它就会调用这个工具的 _arun 方法。

有了这个前提,我们再去看我们之前的mcp工具,就会发现几个问题 首先,对于我们之前调用的mcptool,tool包括以下几个参数

python 复制代码
mcp_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema,
            }

也就是包括名字,描述,参数输入格式 但是在agent中,这样的json格式已经不能被识别了,也就是我们需要构建一个格式化的接口

首先我们按照以前的教程构建一个mcp服务,这个服务包括以下参数,这里的tool是从mcp服务器返回的原始数据,原始数据如下,假设我们这个mcp只需要输入username和filename(这个是在mcp服务中构建的,详细请参阅上述文章)

python 复制代码
{
  "type": "function",
  "function": {
    "name": "xx",
    "description": "xx",
    "inputScheme": {
        "username": {
          "type": "string"
        },
        "filename": {
          "type": "string"
        }
      },
      "required": ["username"]
    }
}

我们将原始数据进行处理为以下这个更加简洁的结构

python 复制代码
mcp_tools = [{
            "type": "function",
            "function": {
                "name": tool.name,
                "description": tool.description,
                "parameters": tool.inputSchema,
            }
        } for tool in tools_response.tools]

        mcp_session = session

首先是进行agent中的args_schema构建

python 复制代码
def create_pydantic(schema: dict, model_name: str) -> Type[BaseModel]:
	# 这里创建一个返回字典
	fields = {}
	# 这里从mcptool的参数进行一步步解析
	# 从上述的参数中获取这两个值
    properties = schema.get('properties', {})
    required_fields = schema.get('required', [])
    # 进行字典的解包推导
    for prop_name, prop_schema in properties.items():
        # 这里对类型做了简化映射,可以根据未来工具的复杂性进行扩展
        field_type: Type[Any] = str  # 默认为字符串类型
        if prop_schema.get('type') == 'integer':
            field_type = int
        elif prop_schema.get('type') == 'number':
            field_type = float
        elif prop_schema.get('type') == 'boolean':
            field_type = bool
        
        # Pydantic 的 create_model 需要一个元组: (类型, 默认值)
        # 对于必需字段,默认值是 ... (Ellipsis)
        if prop_name in required_fields:
            fields[prop_name] = (field_type, ...)
        else:
            fields[prop_name] = (field_type, None)
    
    
    # 使用 Pydantic 的 create_model 动态创建模型类
    #
    return create_model(model_name, **fields)

create_model 函数在接收到这些参数后,会返回一个全新的类 也就是会创建一个这样的类,用户规定格式

python 复制代码
class moedelname(BaseModel):
	filename:str
	username:str

创建完这样一个格式接口之后,我们还需要一个mcp和agent的通信,也就是前面提到的_run 需要定义一个类,继承于BaseTool 这个类首先必须定义四种参数,然后就会利用mcp中的call_tool进行调用mcp,对于返回的文本,直接返回一个纯文本。对于其他格式的mcp,则更具mcp类型返回不同的格式

content: list[TextContent | ImageContent | EmbeddedResource]

python 复制代码
class Mcptool(Basetool):
	name: str
    description: str
    args_schema: Type[BaseModel]  # 强制工具必须有参数结构
    session: "ClientSession"      # 类型前向引用
    # 这里我们使用异步的方法,由于在mcp中采用的是异步处理,防止阻塞主程序,一般来说使用_run即可
    # 这里的**kawrg是任意项的参数解包
	async def _arun(self,**kwarg:any) -> any:
		# 这里同样使用await参数进行异步调用
		# 这里就是mcp连接中的call_tool调用
		response_obj = await self.session.call_tool(self.name,kwargs)
		# 返回纯文本内容
		# 这里一班包括了content: list[TextContent | ImageContent | EmbeddedResource]
		function_response_text = response_obj.content[0].text
		return function_response_text
	

2. agent主程序的构建

接下来将进行agent主程序的构建

  1. 获取上下文记录 ,这里可以采用langchain架构的历史记录获取,也可以直接获取数据库中的数据,这里不做讲解history_messages_raw = get_chat_history(session_id, user_id, limit=20)

  2. 初始化llm的连接: 获取llm的基础连接细信息,包括密码,模型,温度。注意目前agent不支持流式输出

    python 复制代码
    llm = ChatOpenAI(
            model=current_model,
            base_url=BASE_URL,
            api_key=apikey,
            temperature=0,
            streaming=False,
        )
  3. 遍历工具箱 首先创建一个tool,这个列表存放着的是一个个类 然后对所有的mcptool进行遍历,遍历完之后,将工具的名称、描述、刚刚创建的参数模型类 (args_schema),以及与服务器的连接 (mcp_session) 等信息,打包成一个 McpTool 类的实例。 最后,将这个 McpTool 实例添加到一个名为 langchain_tools 的列表中。现在,我们就有了一个 LangChain Agent 完全能理解的"工具箱"。

python 复制代码
	langchain_tools: List[BaseTool] = []
	    for tool_def in mcp_tools:
	        func_info = tool_def["function"]
	        # 为动态创建的 Pydantic 模型生成一个唯一的类名
	        model_name = f"{func_info['name'].replace('_', ' ').title().replace(' ', '')}Input"
	        # 这里构建一个类,名字的自定义的,参数就是通过mcp_tool获取的
	        args_schema = create_pydantic(func_info["parameters"], model_name)
	        # 这里通过类进行初始化
	        # 
	        langchain_tool = McpTool(
	            name=func_info["name"],
	            description=func_info["description"],
	            args_schema=args_schema,
	            session=mcp_session,
	            username=username
	        )
	        langchain_tools.append(langchain_tool)
  1. 创建一个agent prompt 我们将历史记录和userinput传给agent 这里的MessagesPlaceholder(variable_name="agent_scratchpad")指的是agent的内部运作空间

    python 复制代码
    prompt = ChatPromptTemplate.from_messages([
            ("system", "你是一个有用的工程师助手,可以使用工具来获取额外信息。"
                       "你的主要任务是分析工具返回的JSON数据,并以详细的自然语言(根据用户给你的语言风格)向用户总结关键发现。"
                       "现在有一下几个要求:1.不要在你的回答中逐字重复整个JSON数据。请根据上下文进行回复。2. 请使用Markdown格式(例如,使用项目符号、加粗、表格等)来组织你的回答 3. 回答需要依照因果推断相关的知识和术语进行回答"),
            MessagesPlaceholder(variable_name="chat_history"),
            ("user", "{input}"),
            MessagesPlaceholder(variable_name="agent_scratchpad"), # Agent 内部工作空间
        ])
  2. 创建 Agent Agent (create_openai_tools_agent):这是一个"大脑"。它由三部分组成:LLM、工具箱(langchain_tools)和提示(Prompt)。它知道如何根据提示和用户的输入来决定调用哪个工具。

    python 复制代码
    agent = create_openai_tools_agent(llm, langchain_tools, prompt)
  3. 创建执行器 有了agent,我们就开始执行

    python 复制代码
    agent_executor = AgentExecutor(
            agent=agent, 
            tools=langchain_tools, 
            verbose=True, # verbose=True 会在日志中打印 Agent 的思考过程
            return_intermediate_steps=True # 打印中间的工具调用步骤
        )
  4. 格式化历史记录 在langchain中,如果使用其他格式的历史记录,都需要格式化历史记录信息,包括(HumanMessage, AIMessage)

    python 复制代码
    HumanMseeage(content = msg["content"] if msg["role"] = 'user')
    else AImseeage(conent = mag["content"])
    for msg in history
  5. 调用agent 这部分是agent核心,流程如下

    • 思考:执行官将用户的请求、对话历史和工具列表(包含工具的description)一起打包,发送给 LLM。LLM 分析后认为,用户的意图是"某功能",并且 mcp中的工具的描述最匹配这个意图。LLM 还会从用户输入中提取参数,比如 filename。
    • 行动 :Agent 决定调用 mcp的对应 工具,并附带提取出的参数。
    • 执行:Agent Executor 找到对应的 McpTool 实例,并调用其 _arun 方法。
    • 桥接 (_arun 方法):_arun 方法通过 mcp_session 向 mcp_server.py 发起真正的工具调用请求。
    • MCP 服务器工作,返回参数或者其他
    • 观察 :_arun 方法接收到服务器返回的 JSON,将其作为字符串返回给 Agent Executor。
    • 再次思考:Agent Executor 将工具返回的结果(观察)再次发给 LLM,说:"我调用工具后得到了这个 JSON,请你根据它,并结合我们之前的对话,生成一个最终的、对用户友好的回复。"
    • 最终输出:通过prompt,LLM 生成一段总结性的文字,例如:"好的,我已经完成了因果分析。结果显示,干预变量对结果变量有显著的正向影响..."。
    python 复制代码
    agent_response = await agent_executor.ainvoke({
            "input": text,
            "chat_history": chat_history
        })
    final_output_summary = agent_response.get("output", "抱歉,我在处理时遇到了问题。")

3. 观察总结agent调用

我们打印出agent的中间过程就会发现包括以下几个部分

  1. 'chat_history': [ HumanMessage(content='你好', ...), AIMessage(content='你好!有什么我可以 帮助你的吗?', ...), // ... 大量的历史消息 ... ] 这里的就是所有的已经格式化的历史消息

  2. 'intermediate_steps': [ ( ToolAgentAction(...), '{"success": true, ... }' ) ] 这里就是工具的调用,我们进行拆解 这里的是agent的行动日志,其中包括了一个mcp_tool,然后包括了参数的输出 这整个字典就是传递给 McpTool._arun 方法的 kwargs。

    python 复制代码
    ToolAgentAction(
        tool='perform_causal_analysis', 
        tool_input={'filename': '1.csv', 'username': 'test_user'}, 
        log="...", 
        message_log=[...], 
        tool_call_id=''
    )

    接下来就是'{"success": true, "message": "因果分析成功完成。", "data": {"nodes": [...], "edges": [...]}, "raw_results": {...}, "analyzed_filename": "1.csv"}' 这里就是agent调用完mcp之后的结果,那么我们可以看到这里有McpTool._arun 方法返回的原始字符串。它就是 MCP 服务器 在执行完因果分析后,返回的未经任何处理的 JSON 响应。

  3. 'output':xx这是 LLM 在看到了"行动"(Action)和"观察"(Observation)的全过程后,根据 intermediate_steps 中的信息,并遵循你在 prompt 中设定的指示(例如,"不要逐字重复JSON","使用Markdown格式","用因果术语回答"等),为用户精心生成的、易于理解的自然语言回复。

相关推荐
Baihai_IDP4 小时前
上下文管理策略综述
人工智能·llm
CoderJia程序员甲6 小时前
GitHub 热榜项目 - 日榜(2025-11-24)
ai·开源·llm·github·ai教程
重整旗鼓~18 小时前
1.大模型使用
java·语言模型·langchain
hnode21 小时前
🚀 前端开发者的 AI 入门指南:5 分钟搭建你的第一个 RAG 智能问答系统
langchain
mwq301231 天前
《前端项目技术文档生成器》Prompt(可复用模板)
前端·llm·visual studio code
大模型真好玩1 天前
LangChain1.0实战之多模态RAG系统(二)——多模态RAG系统图片分析与语音转写功能实现
人工智能·langchain·mcp
大模型教程1 天前
谷歌AI Agent技术指南深度解读,从概念到生产
langchain·llm·agent
大模型教程1 天前
一张图拆解 AI Agent 的“五脏六腑”,从感知到进化的完整逻辑!
程序员·llm·agent
爱装代码的小瓶子1 天前
【初识AI】大模型和LangChain?
人工智能·langchain
智泊AI1 天前
预测也用上大模型了!时间序列预测是什么?
llm