使用说明
我从用AI构建MCP客户端中学到了什么
- 我想了解更多关于模型上下文协议(MCP)的信息,所以我学习了 MCP:用Anthropic构建丰富的上下文AI应用程序 由DeepLearning提供的课程。AI.在 创建MCP客户端 在第一课中,他们介绍了一个基于Claude LLM的MCP客户端以及一个提供两个工具的MCP服务器:
search_papers()和extract_info(). - 由于我使用的是OpenAI LLM,我不得不调整MCP客户端代码以与 响应API需要进行许多修改来处理与LLM的通信,包括格式化工具信息、参数化响应、从中提取内容以及准备工具调用结果。
- 我遇到的另一个问题是如何并行化独立的工具调用以加快查询处理。由于现有代码已经基于
async/await,我通过在列表中收集多个工具调用,然后使用await asyncio.gather()Theasyncio.gather()函数会自动将协程或可唤醒函数包装到任务对象中,并安排它们在单线程事件循环中并发运行,而不是并行运行。 - 我最终得到了两个MCP客户端脚本,除了处理与特定LLM通信的部分外,它们共享了许多通用代码。我的下一个目标是封装与不同LLM的通信,这样共享代码只需要编写一次。为了实现这一目标,我创建了一个摘要
LLMAdapter类,并添加了两个具体类,AnthropicAdapter和OpenAIAdapter,实施与各自LLM沟通的细节。 - 将与LLM的通信封装到适配器中是一种体系结构改进,但每当LLM提供程序更改其API时,我仍然必须调整适配器类。在课程中,我了解到MCP规范了客户端和提供工具的服务器之间的通信。这让我产生了完全抽象出LLM特定部分的想法。我通过将MCP客户端重新设计为遵循简单推理和操作(ReAct)模式的代理来实现这一点,并使用 LangChain图形APILLM相关代码减少到只有三个调用:
init_chat_model()使用LangChain的统一接口初始化LLM,bind_tools()连接可用工具,以及ainvoke()打电话给LLM。顺便说一下,框中显示的代理图是通过调用生成的get_graph(xray=True).draw_mermaid_png(). - 因为我显式地实现了工具节点,所以我能够使用
await asyncio.gather()同时调用多个工具。create_react_agent()从langchain.agents这本来是一种用最少的代码构建代理的替代黑盒方法,但它不允许您自定义工具执行策略。明确实现代理的第三种选择是使用 LangGraph的函数式API.与Graph API不同,它允许您通过循环和条件强制定义代理工作流,而不是构建具有节点和边的图。另外,await asyncio.gather()在使用Functional API时不需要,因为@task装饰器自动处理并行执行。 - 打印和调试是快速开发原型时检查中间结果的有用方法。日志记录是一种更高级的监视脚本行为和性能的方法,但它需要一些代码调整,特别是在手动实现时。为了追踪我的脚本,我选择了 LangSmith没有代码更改,因为我已经在LangGraph中使用了LangChain模块(参见
pyproject.toml).完成后 LangSmith文档中的先决条件,我只需按如下方式修改.env文件:setLANGSMITH_TRACING为了实现追踪,OPENAI_API_KEY到我的OpenAI密钥,LANGSMITH_API_KEY从我的LangSmith账户中获得的密钥,以及LANGSMITH_ENDPOINT到https://eu.api.smith.langchain.com因为我用的是欧盟的例子。此外,我设置了LANGSMITH_PROJECT变量,用于对属于同一项目的所有跟踪进行分组。因此,我在LangSmith帐户中获得了每次运行的详细跟踪,包括运行时、令牌计数和每次调用的完整消息堆栈,如上图所示。
正如标题所示,我使用了人工智能辅助环境,如Claude、Cursor和GitHub Copilot来回答我的问题,并根据课程中的MCP客户端实现、我自己的代码片段或LangChain文档中的示例生成代码。我听说一些开发人员声称,多亏了人工智能,他们已经很久没有写过一行代码了。我不能对自己的经历说同样的话。尽管人工智能助手生成了许多有用的代码,可以极大地加快开发人员的工作,但我仔细审查了人工智能生成的代码,包括通过调试,总是发现一些仍需要改进的地方。
