编程助手非常擅长编写使用流行库的代码——这些库正是LLM接受过大量训练的内容。但若指向自定义库、新版本库、内部API或小众框架时,它们的表现就不尽如人意。这对于使用领域专用库或企业代码的团队来说是个难题。
作为库的开发者(LangGraph、LangChain),我们非常关注如何让这些编程助手精通编写LangGraph和LangChain代码。我们尝试了一系列上下文工程技术,有的有效,有的无效。在这篇博文中,我们将分享所进行的实验和获得的经验。我们最大的收获是:
高质量、浓缩的信息,结合按需获取更多细节的工具,产生了最佳效果
仅仅给予助手原始文档访问权限并未如预期般提升性能。实际上,上下文窗口反而更快地被填满。一份简洁、结构化的指南(形式为Claude.md)始终优于单纯接入文档工具。最佳结果来自两者的结合:助手既拥有一些基础知识(通过Claude.md),又能在需要更多信息时访问文档的特定部分。

本文将分享:
- 我们测试的不同Claude Code配置
- 我们用来评估生成代码的评估框架(一个你可用于自己库的可复用模板)
- 结果与关键要点
Claude Code 设置
我们测试了四种不同的配置,为保持一致性,均使用Claude 4 Sonnet模型:
- Claude Vanilla:未经修改的开箱即用Claude Code。
- Claude + MCP:连接到我们的MCPDoc服务器以获取文档访问权限的Claude Code。
- Claude + Claude.md:包含详细LangGraph特定指南的Claude.md文件的Claude Code。
- Claude + MCP + Claude.md:可访问详细Claude.md和MCPDoc服务器的Claude。
用于文档的MCP工具
我们构建MCPDoc服务器是因为我们希望为编程助手提供访问任何库文档的能力。它是一个开源的MCP服务器,提供两个工具:list_doc_sources和fetch_docs。前者分享可用llms.txt文件的URL,后者读取特定的llms.txt文件。在我们的设置中,我们提供了对LangGraph和LangChain的Python及JavaScript文档的访问权限。你可以通过在MCP配置中传入你自己库的llms.txt文件的URL,轻松地将其适配到你的用例。

Claude.md
对于Claude.md,我们创建了一份LangGraph库指南。它包含了常见的LangGraph项目结构要求的详细说明,例如在创建文件前必须进行代码库搜索、正确的导出模式以及部署最佳实践。它包含了构建单代理和多代理系统所需的基本要素的示例代码,例如create_react_agent、监督者模式以及用于动态交接的集群模式。有些实现是LLM难以处理的,例如面向用户代理的流式传输和人在回路(human-in-the-loop)。我们为这些添加了广泛的指南。

我们发现,包含关于常见陷阱和反模式的全面部分特别有价值。这涵盖了常见错误,如不正确的interrupt()用法、错误的状态更新模式、类型假设错误以及过于复杂的实现。这些是我们经常看到LLM犯的错误,原因可能是库已过时或与其他框架的模式混淆。
我们还加入了LangGraph特定的编码标准,如结构化输出验证、正确的消息处理以及其他框架集成调试模式。由于Claude可以访问网络工具,我们在每个部分的末尾添加了特定的文档URL,以供进一步参考和导航指南。
该文件与llms.txt的不同之处在于,后者是包含页面所有内容及URL的纯文本文件,而Claude.md包含的是从零开始时最重要的浓缩信息。正如我们将在结果中看到的,单独传递llms.txt效果并不最佳,因为它有时会用更多的上下文混淆LLM,并且没有关于如何导航和辨别重要信息的指导。
在深入介绍我们的Claude Code配置在不同任务上的表现之前,我们想分享我们用于确定任务完成度和代码质量的评估框架。
评估
我们的目标是衡量对代码质量贡献最大的因素,而不仅仅是功能性。像Pass@k这样的流行指标只捕获功能性,而非最佳实践,后者因上下文而异。
我们构建了一个任务特定的评估工具,既检查技术要求,也检查主观方面,如代码质量、设计选择和对首选方法的遵循程度。
我们为评估定义了三个类别:
冒烟测试 (Smoke Tests)
这些测试验证基本功能。测试确认代码可以编译、暴露.invoke()方法、处理预期的输入状态,并返回预期的输出结构(如具有必需状态属性的AIMessage对象)。
我们使用加权求和计算分数:
分数 = Σᵢ wᵢ × cᵢ
其中 wᵢ 是测试 i 的权重,cᵢ 是测试的二进制结果(通过/不通过)。
任务需求测试 (Task Requirement Tests)
这些测试验证任务特定的功能。测试包括验证部署配置文件、验证发送到外部API(如网络搜索或LLM提供商)的HTTP请求,以及每个编码任务特定的单元测试。评分通过对每个测试结果进行加权求和来完成,与冒烟测试相同。
代码质量和实现评估 (Code Quality & Implementation Evaluation)
对于这个类别,我们使用LLM-as-a-Judge(LLM作为评判员)来捕获二进制测试所遗漏的方面。遵循更好方法的实现应该比仅仅编译和运行的实现得分更高。代码质量、设计选择和对LangGraph抽象的使用都需要细致的评估。
我们审查了每个任务的专家编写代码,并构建了任务特定的评分标准。使用温度为0的Claude Sonnet 4 (claude-sonnet-4-20250514),我们根据这些评分标准评估生成的代码,以专家编写的代码作为参考,并使用人工标注来记录编译和运行时错误。
我们的评分标准有两种类型的标准:
- 客观检查 (Objective Checks):这些是关于代码的二进制事实(例如,存在特定节点、正确的图结构、模块分离、没有测试文件)。LLM评判员对每个检查返回布尔响应,我们使用与冒烟测试相同的加权求和来获得客观检查的分数。
- 主观评估 (Subjective Assessment):这是对代码的定性评估,使用专家编写的代码作为参考,并辅以人工标注来传入编译和运行时错误的日志。LLM评判员识别问题并按严重性(严重、主要、轻微)在两个维度上进行分类:正确性违规和质量问题。
我们对此使用基于罚分的评分:
分数 = 最大分数(Scoreₘₐₓ) - Σₛ (nₛ × pₛ)
其中 Scoreₘₐₓ 是最大可能分数,nₛ 是严重性级别 s 的违规数量,pₛ 是该严重性级别的罚分权重。
总体分数结合了客观和主观结果,公式如下:
总分 = Σᵢ wᵢ × cᵢ + Σₛ (Scoreₘₐₓ,ₛ - Σₛ (nₛ × pₛ))
其中第一项代表客观检查,第二项代表所有主观类别的评估。
我们对每个Claude Code配置的每个任务运行了三次以考虑方差。为保持一致性,所有分数均报告为总可能得分的百分比,然后在任务间取平均值。
你可以使用LangSmith平台复制此方法,以比较编程助手的配置。
结果
我们平均了三个不同LangGraph任务的分数来比较Claude Code配置。下图显示了总体分数:

对我们来说,最有趣的发现是Claude + Claude.md的表现优于Claude + MCP,尽管Claude.md只包含了MCP服务器所能提供信息的一个子集。追踪记录解释了原因:Claude并没有像我们预期的那样频繁调用MCP工具。即使任务需要跟踪两三个链接页面,它通常也只调用一次MCP并在主页面停止,而这只能提供表面描述,而非所需的细节。
相比之下,Claude + Claude.md + MCP更有效地使用了文档。我们在追踪记录中观察到,它更频繁地调用MCP工具,甚至在需要时触发网络搜索工具。这种行为是由Claude.md驱动的,它在每个部分的末尾包含了供查找进一步信息的参考URL。
这并不意味着MCP工具本身没有帮助。它们将分数提高了约10个百分点,主要是通过让助手掌握基本语法和概念来实现的。但对于任务完成度和代码质量而言,Claude.md更为重要。该指南包含了需要避免的陷阱和需要遵循的原则,这有助于Claude Code更好地思考,并探索库的不同部分,而不是停留在高级描述上。
这些结果为我们提供了为任何人配置编程助手的一些更广泛的经验教训。
关键要点
这些结果给我们留下了一些要点。如果你正在考虑为你自己的库定制编程助手,以下内容可能会有所帮助:
- 上下文过载 (Context Overload):转储文档中的大型llms.txt文件会挤占上下文窗口。这可能导致性能下降和成本升高。我们的MCP服务器在获取页面内容方面有一个简单的实现(完全获取)。即使只调用一次,也会触发Claude Code关于上下文窗口即将填满的警告。如果你的文档足够广泛,以至于需要工具来检索特定文档,那么值得构建更智能的检索工具,只提取相关的代码片段。
- Claude.md的回报最高 (Claude.md has the highest payoff):它比设置MCP服务器或特定工具更容易,且运行成本更低。在任务#2上,Claude + Claude.md的成本比Claude MCP和Claude + Claude.md + MCP低约2.5倍。它比Claude MCP更便宜且性能更好。这是在考虑定制Claude Code时一个很好的起点,并且对于某些用例来说可能已经足够好了。
- 编写好的指南 (Write good instructions):一份Claude.md(或Agents.md)应该突出显示你的库的核心概念、独特功能和常见基本要素。手动审查失败的运行,以找出反复出现的陷阱并为其添加指导。对我们来说,这意味着涵盖LangGraph中与Streamlit结合的异步任务,代理经常在asyncio集成上失败。我们还添加了启动开发服务器的调试步骤,这修复了导入错误,并让Claude Code发送请求到服务器以验证输出。流行的代码生成工具通常使用长的系统提示(7-10k个词元)。在指南上投入精力会有很好的回报。
- Claude + Claude.md + MCP 胜出 (Claude + Claude.md + MCP wins):虽然Claude.md的每词元效益最高,但最强的结果来自将其与允许详细阅读文档的MCP服务器配对。指南提供了概念上的导向,而文档则有助于深入细节。两者结合可以在领域特定库上产生最佳结果。
在附录中,我们包含了每项任务的结果和类别级别的图表,供希望深入了解每项任务性能的读者参考。
附录
任务 #1:文本转SQL代理 (Text-to-SQL Agent)
我们要求每种配置构建一个基于LangGraph的文本转SQL代理,该代理能够从自然语言生成SQL查询,针对数据库执行该查询,并返回自然语言响应。此任务需要从远程URL获取Chinook SQLite数据库并设置内存数据库。你可以在此处阅读我们传递给Claude Code实例的提示。
对于此任务,我们的冒烟测试验证了基本的LangGraph功能。任务需求检查了数据库设置;对简单查询、连接查询、日期范围查询的SQL查询处理;LLM-as-a-Judge评估了代码设计选择,例如远程URL获取、用于SQL生成、执行和响应的独立节点。LLM-as-a-Judge的提示可在此处获取。
结果显示了不同Claude Code配置和类别之间的性能差异:


较差的实现通常难以跨线程连接内存数据库、在LLM提示中下载和硬编码模式而不是使用带有运行时模式读取的远程URL,并且未能正确解析LLM输出以执行SQL(当LLM生成格式略有不同的结果时会中断)。
任务 #2:公司研究员 (Company Researcher)
对于此任务,我们要求每种Claude配置构建一个多节点LangGraph代理,该代理使用通过Tavily API进行的网络搜索来研究公司。该代理需要处理结构化数据收集、实现并行搜索执行,并添加一个反思步骤以确保收集所有请求的信息。你可以在此处阅读提示。
我们的测试验证了基本功能、Tavily API集成以及结构化对象类中所有请求属性的存在。LLM-as-a-Judge检查了诸如反思逻辑实现、最小搜索查询限制和并行网络搜索执行等功能的实现。
以下是此任务的结果:


大多数实现失败与在状态对象中构建信息和反思步骤有关。较差的实现要么没有功能正常的反思节点,要么未能触发额外的搜索。
任务 #3:记忆类别 (Categories of Memories)
这是一个编辑任务,我们向每种Claude Code配置提供了一个现有的记忆代理作为基础代码。我们要求它们扩展记忆存储方法,除了用户ID外,还要按类型(个人、专业、其他)对记忆进行分类;实现基于消息类别(而不仅仅是用户ID)的选择性记忆检索;并在保存记忆之前添加人在回路确认步骤。我们还故意添加了语法错误。完整提示可在此处获取。
通过测试,我们验证了实现是否正确地在记忆存储之前添加了中断功能、实现了按类别存储和检索、使用了三个特定类别(个人、专业、其他),并保持了功能正常的中断逻辑(仅在用户接受时保存记忆)。LLM-as-a-Judge评估了实现是否使用基于LLM的分类而不是脆弱的关键字匹配,以及是否没有不必要的文件。
对于一个编辑任务,我们看到以下结果:


大多数实现难以正确实现中断功能。错误的实现要么添加简单的input()调用来获取终端输入,要么通过创建单独的节点而不是使用几行正确的中断逻辑来使解决方案过度复杂化。较差的实现还依赖关键字匹配进行分类而不是基于LLM的分类,并且几乎所有的实现都未能捕获我们故意包含的语法错误。(来源)















