跳到正文
北京时间
原文
LangChain:Blog·· 2025-11-06精选AI 评分63

LangChain 重建 Chat LangChain:放弃向量嵌入,改用直接 API 访问与 Deep Agent 子图

Why We Rebuilt LangChain’s Chatbot and What We Learned

AI 导读

LangChain 重建了 chat.langchain.com,弃用分块加向量数据库的检索方案,改为让智能体通过 Mintlify API、知识库标题扫描和 ripgrep 代码搜索直接访问文档、知识库和代码库的原始结构。

推荐理由

原文复盘了放弃向量嵌入、改用直接 API 访问和子图分域的重建思路,含工具设计与性能数字,方法可迁移到类似问答智能体。

正文 · AI 翻译

作者:Liam Bush

背景

每个成功的平台都需要可靠的支持,但我们意识到自己的团队正花费大量时间追踪技术问题的答案。这种摩擦不仅拖慢了我们工程师的速度——它还是我们用户的一个关键瓶颈。

我们着手用我们所推崇的工具来解决这个问题:LangChain、LangGraph 和 LangSmith。我们最初构建 chat.langchain.com 时是作为一个原型,明确设计用于实现两个功能:

  1. 产品问答:帮助用户——以及我们自己的团队——即时获得关于产品问题的权威答案。
  2. 客户原型:作为一个生动的示例,展示客户如何使用 LangChain 技术栈构建复杂、可靠的智能体。

我们有强烈的意图,也有一个可用的产品。但我们要坦白:我们的支持工程师并没有积极使用 LangChain Chatbot。这正是我们真正学习的起点。这就是我们如何修复自己的智能体的故事——以及我们在构建真正可靠、生产级应用方面学到的经验,这些应用可供我们的客户借鉴和使用。

我们的团队没有积极使用 Chat LangChain,不是因为它坏了,也不是因为他们不相信它。而是因为当有人问“为什么流式传输在生产环境中不工作?”时,他们需要比仅仅把文档作为唯一资源更全面的东西。我们都知道文档永远不够。

于是他们构建了自己的工作流程:

  • 第 1 步:搜索我们的文档(docs.langchain.com),了解该功能应该做什么。
  • 第 2 步:检查我们的知识库(support.langchain.com),看看其他用户是否遇到同样的问题以及如何解决的。
  • 第 3 步:打开 Claude Code,搜索实际实现,并验证代码实际做了什么。

文档提供官方说法。知识库提供现实世界的问题。代码库提供事实真相。

我们决定将其自动化

这个三步仪式效果出奇地好。我们看着他们每天做几十次,然后想:如果我们把这个确切的工作流程自动化会怎样?

于是我们构建了一个内部 Deep Agent(一个用于构建能够处理复杂、多步骤任务的智能体的库),包含三个专门的子智能体——一个负责文档,一个负责知识库,一个负责代码库搜索——每个都会提出后续问题并过滤结果,然后再将洞察传递给主编排智能体。

主智能体会综合所有内容,并给出这样的答案:

Example output:

“要从子图进行流式传输,请根据 LangGraph 流式传输文档 在你的流配置中设置 subgraphs: true。有一篇支持文章标题为 ['为什么升级后 token 流式传输不工作](https://support.langchain.com/articles/7150806184-Why-is-token-by-token-streaming-not-working-after-upgrading-LangGraph?)[?](https://www.notion.so/263808527b1780db9f26fa75aed5e7e3?pvs=21)' 解释了这个问题——你需要启用子图流式传输,才能从嵌套智能体获得 token 级别的更新。实现在 pregel/main.py 第 3373-3279 行, 其中 subgraphs 标志控制嵌套图的输出是否包含在流中。”

我们的工程师很喜欢它。

它每周在复杂调试上为他们节省了数小时。他们只需描述一个生产环境问题,就能得到一个全面的回答,其中引用了文档、参考了已知解决方案,并指出了真正关键的代码行。

然后我们有了一个顿悟

接着有人问了一个显而易见的问题:如果这对我们如此有效,为什么我们公开的 Chat LangChain 不这样运作呢?

这话说得有道理。我们的公开工具把文档切分成片段,生成嵌入向量,再存入向量数据库。随着文档更新,我们不得不不断重建索引。用户得到了答案,但引用信息有待完善,上下文也是碎片化的。

我们无意中在内部构建出了更好的东西,仅仅是因为照搬了行之有效的做法。是时候把同样的方法带到公开产品中了。

当我们开始重建时,很快意识到需要结合两种不同的架构,它们由两大类问题驱动。大多数问题可以用文档和知识库来回答。其余的则需要对代码基础进行分析。

我们如何构建新的 Agent

针对简单文档:创建 Agent

我们为 chat.langchain.com 选择了 createAgent(langchain 中的 Agent 抽象)作为默认模式,因为它最适合速度。

没有规划阶段,没有编排开销——只有即时的工具调用和回答。Agent 搜索文档,必要时检查知识库,如果结果不清晰就优化查询,然后返回答案。大多数文档问题可以用 3-6 次工具调用处理,而 Create Agent 能在几秒内执行完这些调用。

模型选项:

我们让最终用户可以使用多个模型——Claude Haiku 4.5、GPT-4o Mini 和 GPT-4o-nano——我们发现 Haiku 4.5 在工具调用方面异常快速,同时保持了很强的准确性。createAgent 与 Haiku 4.5 的组合为大多数查询带来了15 秒以内的响应,这正是文档问答所需要的。

我们如何优化它:

我们使用 LangSmith 追踪每一次对话,找出 Agent 在哪些地方进行了不必要的工具调用,并改进我们的提示词。数据表明,如果我们教会 Agent 提出更好的追问,大多数问题都可以用 3-6 次工具调用回答。LangSmith 的评估套件让我们能够对不同的提示策略进行 A/B 测试,并衡量速度和准确性方面的改进。

这段 30 秒的追踪包含 7 次工具调用:4 次文档搜索、1 次知识库文章查找和 2 次文章阅读,其中 20 秒用于流式传输最终响应。在此查看

使用代码回答:带子图的 Deep Agent

很多问题除了利用文档、知识库和交叉引用已知问题作为资源外,还需要深入搜索我们的代码库以验证实现细节。

架构:

对于这些任务,我们构建了一个带有专用子图的 Deep Agent:一个用于文档搜索,一个用于知识库搜索,还有一个用于代码库搜索。

每个子代理独立运作,提出追问、筛选信息,并在将最相关的见解传递给主编排代理之前只提取这些见解。这可以防止主代理淹没在上下文中,同时允许每个领域专家按需深入挖掘。

代码库搜索优势:

代码库搜索子代理尤为强大。它可以使用模式匹配搜索我们的私有仓库,浏览文件结构以理解上下文,并以行号精度读取具体实现。

权衡:

这种深度代理架构运行时间更长——对于复杂查询有时需要 1-3 分钟——但彻底性是值得的。当初始响应没有解决核心问题时,我们会使用 DeepAgent。

免责声明:此模式在发布时仅对部分用户启用,将在几天内全面开放。

为什么我们放弃了向量嵌入

文档搜索的标准方法——将文档分块、生成嵌入、存入向量数据库、按相似度检索——对于 PDF 等非结构化内容效果不错。但对于结构化的产品文档,我们不断遇到三个问题。

分块破坏结构。当你把文档切成 500 个 token 的片段时,你会丢失标题、子章节和上下文。代理会引用 "set streaming=True" 却不解释原因或时机。用户不得不翻遍页面才能找到所需内容。

持续重新索引。我们的文档每天更新多次。每次更改都意味着重新分块、重新嵌入和重新上传。这拖慢了我们的速度。

引用模糊。用户无法验证答案或追溯信息来源。

突破在于意识到我们解决的是错误的问题。文档已经组织好了。知识库已经分类了。代码库已经可导航了。我们不需要更智能的检索——我们需要让代理直接访问现有的结构。

更好的方法:直接 API 访问和智能提示

我们没有分块和嵌入,而是让代理直接访问真实内容。对于文档,我们使用 Mintlify's API,它返回完整页面,所有标题、子章节和代码示例都完好无损。对于知识库,我们首先按标题查询支持文章,然后完整阅读最有希望的那些。对于代码库搜索,我们将代码库上传到我们的 LangGraph Cloud 部署,并使用 ripgrep 进行模式匹配、目录遍历以理解结构,以及文件读取以提取具体实现。

代理不基于相似度分数进行检索。它像人类一样搜索——使用关键词、细化和后续问题。

这就是神奇之处。我们不只是告诉代理搜索一次并返回它找到的任何内容。我们提示它批判性地思考自己是否有足够的信息。如果结果模糊或不完整,代理会细化查询并再次搜索。如果文档提到一个概念却没有解释,代理会专门搜索该概念。如果存在多种解释,代理会缩小到最相关的一种。

工具设计:为人类工作流而构建

我们设计的工具模仿人类实际的搜索方式,而不是检索算法的工作方式。

文档搜索:完整页面,而非片段

文档搜索工具查询 Mintlify's API 并返回完整页面。当有人询问流式传输时,代理不会得到来自不同章节的三段不连贯的段落——它会得到整个流式传输文档页面,结构完全符合人类阅读的方式。

@tool
def SearchDocsByLangChain(query: str, page_size: int = 5, language: Optional[str] = None) -> str:
   """Search LangChain documentation via Mintlify API"""
   params = {"query": query, "page_size": page_size}
   if language:
       params["language"] = language
   response = requests.get(MINTLIFY_API_URL, params=params)
   return _format_search_results(response.json())

但我们不止步于此。我们提示智能体评估初始结果是否真正回答了问题。这是正确的章节吗?是否有需要澄清的相关概念?更具体的搜索词会更好吗?

智能体有4-6 次工具调用的预算,我们鼓励它策略性地使用这些调用来在回答之前完善其理解。

以下是实际应用中的样子:

用户问,“如何为我的智能体添加记忆?”

智能体搜索"memory",得到的结果涵盖检查点、对话历史和 Store API。智能体没有随机选择一个,而是意识到问题有歧义——记忆可能意味着在线程内持久化对话状态,也可能意味着跨多个对话存储事实。

它再次用"checkpointing"搜索以缩小线程级持久化的范围,获取支持文章“如何在 LangGraph 中配置检查点?”,并认识到它不涵盖跨线程记忆。

于是它搜索"store API"来填补空白。

最终答案涵盖了用于对话历史的检查点和用于长期记忆的 Store API,并精确引用了所使用的支持文章和文档。

这个迭代搜索过程在 Create Agent 中只需几秒钟,但它从根本上改变了响应的质量。智能体不仅仅是在检索——它在推理用户真正需要什么。

知识库搜索:先扫描,再阅读

我们将知识库(由 Pylon 提供支持)搜索构建为两步过程,因为这就是人类使用知识库的方式。

首先,智能体检索文章标题——有时有几十个——并扫描它们以确定哪些看起来相关。然后它只完整阅读那些文章。

@tool
def search_support_articles(collections: str = "all", limit: int = 50) -> str:
   """Step 1: Get article titles to scan"""
   articles = pylon_client.list_articles(collections=collections, limit=limit)
   return json.dumps([{
       "id": a["id"],
       "title": a["title"],
       "url": a["url"]
   } for a in articles])

@tool
def get_article_content(article_ids: List[str]) -> str:
   """Step 2: Read the most relevant articles"""
   articles = pylon_client.get_articles(article_ids)
   return "\\n\\n---\\n\\n".join([
       f"# {a['title']}\\n\\n{a['content']}\\n\\nSource: {a['url']}"
       for a in articles
   ])

为什么这有效:

这防止智能体淹没在信息中。智能体不是将 30 篇完整文章传递到上下文窗口,而是过滤到真正重要的 2-3 篇,彻底阅读它们,并提取关键见解。

提示强化了这一点:注重质量而非数量,如果需要就缩小搜索范围,只返回直接回答问题的信息。

代码库搜索:搜索、导航、验证

这就是我们的Deep Agent大放异彩的地方。

我们给智能体提供了三个工具,它们反映了开篇的工作流程——与我们的工程师使用Claude Code时遵循的相同模式:

@tool
def search_public_code(pattern: str, path: Optional[str] = None) -> str:
   """Step 1: Find code matching a pattern"""
   cmd = ["rg", pattern, str(path or search_path)]
   return subprocess.run(cmd, capture_output=True, text=True).stdout

@tool
def list_public_directory(path: str, max_depth: int = 2) -> str:
   """Step 2: Understand the file structure"""
   cmd = ["tree", "-L", str(max_depth), str(path)]
   return subprocess.run(cmd, capture_output=True, text=True).stdout

@tool
def read_public_file(file_path: str, start_line: int = 1, num_lines: int = 100) -> str:
   """Step 3: Read the actual implementation"""
   with open(file_path, "r") as f:
       lines = f.readlines()
   return "\\n".join(lines[start_line-1:start_line-1+num_lines])

工作原理:

首先,它使用ripgrep在代码库中搜索模式。然后它列出目录结构以了解文件是如何组织的。最后,它读取特定文件,聚焦于相关部分,并返回带有行号的实现。

真实世界示例:

用户报告流式令牌在生产环境中挂起。文档子智能体发现流式配置涉及缓冲区设置。知识库子智能体呈现了一篇关于升级后令牌流式问题的支持文章。

但代码库子智能体是找到实际实现的那个——它搜索"streaming buffer",导航到callbacks/streaming.py,并返回第 47-83 行,其中默认缓冲区大小是硬编码的。

这就是解决真实问题的那种深入调查。

区别是什么?Deep Agent可以跨所有三个领域并行工作,并将临时发现总结为一个连贯的答案。

Deep Agent 和子图如何解决上下文过载

当我们最初把深度智能体构建为一个可以访问全部三种工具的单体系统时,它会把它找到的所有内容都返回。主智能体会一次性收到五个文档页面、十二篇知识库文章和二十个代码片段。

上下文窗口会爆炸,最终回复要么充斥着无关细节,要么完全错过关键洞见。

于是我们用专门的子图对其进行了重构。

工作原理:

每个子智能体独立运行。它搜索自己的领域,提出后续问题以澄清歧义,筛选结果,并只提取黄金数据:回答问题所需的关键事实、引用和上下文。

主编排智能体从不接触原始搜索结果。它只接收来自各领域专家提炼后的洞见。查看完整追踪以及提示词**此处。

为什么这很重要:

文档子智能体可能阅读五个完整页面,但只返回两个关键段落。知识库子智能体可能扫描二十个文章标题,但只返回三个相关摘要。代码库子智能体可能搜索五十个文件,但只返回带有行号的具体实现。

主智能体获得干净、经过整理的信息,可以将其综合成一个全面的答案。

使其达到生产就绪

即使优雅的智能体设计也需要生产基础设施,才能在接触真实用户时存活下来。我们构建了模块化的中间件来处理那些否则会弄乱我们提示词的运维问题。

middleware = [
   guardrails_middleware,      # Filter off-topic queries
   model_retry_middleware,     # Retry on API failures
   model_fallback_middleware,  # Switch models if needed
   anthropic_cache_middleware  # Cache expensive calls
]

每一层的作用:

护栏过滤掉离题查询,使智能体专注于 LangChain 问题。

重试中间件优雅地处理临时 API 故障,因此用户永远不会看到晦涩的错误消息。

回退中间件在某个模型不可用时,在 Haiku、GPT-4o Mini 和 Gemini Nano 之间切换。

缓存通过复用相同查询的结果来降低成本。

这些层对用户不可见,但它们对可靠性至关重要。它们让智能体专注于推理,而由基础设施处理故障模式、成本优化和质量控制。

将智能体交付给用户

构建一个出色的智能体只是战斗的一半。另一半呢?以感觉快速且智能的方式将其交付给用户。

我们使用 LangGraph SDK 来处理流式传输和状态管理的所有复杂性。

加载用户线程:

当有人打开 Chat LangChain 时,我们使用 LangGraph SDK 获取其对话历史:

const userThreads = await client.threads.search({
 metadata: { user_id: userId },
 limit: THREAD_FETCH_LIMIT,
})

每个线程都在元数据中存储用户 ID,因此对话在跨会话时保持私密且持久。LangGraph SDK 会自动处理过滤。

实时流式传输响应:

当用户发送消息时,LangGraph SDK 会在生成响应时流式传输它:

typescript

const streamResponse = client.runs.stream(threadId, "docs_agent", {
 input: { messages: [{ role: "user", content: userMessage }] },
 streamMode: ["values", "updates", "messages"],
 streamSubgraphs: true,
})

for await (const chunk of streamResponse) {
 if (chunk.event === "messages/partial") {
   setMessages(prev => updateWithPartialContent(chunk.data.content))
 }
}

用户看到的内容:

三种流模式展示智能体的整个思考过程:

  • messages — 令牌随着智能体写作逐步出现
  • updates — 工具调用揭示智能体正在搜索什么
  • values — 处理后的最终完整状态

用户看着智能体思考、搜索文档、检查知识库,并逐令牌构建响应。没有加载转圈。

对话记忆

在消息之间传递同一个 thread_id,LangGraph 的检查点机制会处理其余部分。它存储对话历史,为每一轮检索上下文,并跨会话维护状态。我们设置了 7 天的 TTL。就这样。

成果

自新系统上线以来,我们看到了显著的改进。

对于公开的 Chat LangChain,用户可以获得15 秒以内的响应,并附有精确的引用。他们可以立即验证答案,因为我们会直接链接到相关的文档页面或知识库文章。而且我们不再花费数小时重新索引——文档会自动更新。

在内部,我们的支持工程师使用 Deep Agent 来处理最复杂的工单。它会搜索文档、交叉比对已知问题,并深入我们的私有代码库,找出真正能解释当前情况的实现细节。该智能体不会取代我们的工程师——它是在增强他们,承担研究性工作,让他们能够专注于解决问题。

关键要点

  • 遵循用户的工作流程:不要重新发明轮子;将你最优秀的用户(或内部专家)已经在使用的成功工作流程自动化。对于 LangChain 来说,这意味着复刻检查文档、知识库和代码库这三步仪式。
  • 评估向量嵌入是否合适:对于产品文档和代码这类结构化内容,使用向量嵌入可能会破坏文档结构,导致引用含糊不清,并且需要不断重新索引。向量嵌入非常适合非结构化内容、较短的文本块或聚类用例。
  • 让智能体直接访问结构:这种方法允许智能体通过 API 直接访问内容现有的结构。这让智能体能够像人类一样搜索,使用关键词并进行细化。
  • 优先考虑推理而非检索:设计工具时应模拟人类的工作流程:先浏览文章标题再阅读内容,并对代码使用模式匹配和目录导航。提示智能体在初始结果含糊不清时提出后续问题并细化查询,确保最终答案覆盖用户的真实需求。
  • 使用 Deep Agents 和子图来管理上下文:对于复杂的多领域问题,使用带有专门子图的 Deep Agent 可以防止主编排智能体淹没在原始搜索结果中。每个子智能体只过滤并提取其领域中的“黄金数据”,然后再将提炼后的洞见向上传递。
  • 生产级中间件的必要性:即使设计优雅的智能体也需要稳健的基础设施才能可靠运行。实现模块化中间件,用于护栏(过滤无关查询)、重试(应对 API 故障)、回退(切换模型)和缓存,对于生产级可靠性、成本优化和质量控制至关重要。

下一步

公开代码库搜索(将在未来几天内上线)——当文档和知识库不够用时,智能体将搜索我们的公开仓库,以验证实现并引用确切的行号

亲自尝试

Chat LangChain 已在 chat.langchain.com 上线。使用 Claude Haiku 4.5 尝试以获得最快的响应,或试用 GPT-5 Mini 和 GPT-5 Nano 来了解不同模型的表现。

加入讨论

构建能在速度与深度之间取得平衡的智能体很难,我们仍在学习。如果你也在研究类似问题,我们很想听听你的发现。

加入我们的 论坛 LangChain 社区,或在 Twitter 上关注我们。

订阅我们的新闻通讯,获取团队和社区的最新动态。

来源:LangChain:Blog · langchain.com