Sunday 的面试指南

LangChain Middleware 是什么?和 Tool、Callback 有什么区别?

🧑‍💻 面试官:你想给 Agent 加模型重试和动态选型,会把它们写成工具吗?

🙋‍♂️ 我:不太合适。工具是模型为了完成任务选择调用的能力,重试和模型选择更适合由应用控制调用过程。

🧑‍💻 面试官:用 LangChain Middleware 可以吗?

🙋‍♂️ 我:可以。它能在 Agent 的一些执行环节介入,例如调用模型之前,或者包住一次模型、工具调用。

🧑‍💻 面试官:那和 Callback 有什么区别?如果外面还有一层缓存,中间件换个顺序,权限检查会不会被跳过?

这道题先看代码站在哪里:是在完成业务动作,观察一次调用,还是有权决定这次调用怎样继续。再看它能管到哪些路径。

面试速答(60 秒版)

LangChain Middleware 是介入 Agent 执行过程的一组钩子。可以在调用模型前整理输入,也可以包住模型或工具调用,处理重试、路由和提前结束等逻辑。

Tool 负责完成具体业务动作,例如搜索资料、查询订单。Middleware 不一定提供新的业务能力,更多是在控制这些调用怎样发生。

Callback 主要用于接收开始、结束、错误等事件,方便记录日志和追踪。观察事件和修改调用流程不是一回事,不能把普通回调当成通用的执行控制接口。

实际使用时,还要关注中间件顺序。外层包住内层,可能影响缓存、鉴权和重试执行多少次。它只能控制经过自身的路径;业务服务仍应检查权限,写工具的重试也要考虑幂等,不能因为放进中间件就自动安全。

面试速答总览:中间件控制调用过程,工具实际搜索资料,回调在旁记录开始结束和错误事件

知识点详解:同样在调用前做事,职责为什么不一样?

先放回一轮 Agent 执行里看

假设咱们在做报告助手。模型根据主题决定搜索资料,应用执行搜索工具,再把结果交给模型,让它继续整理报告。

这条链路里,“搜索资料”是业务能力,适合做成 Tool。

但还有一些工作不是报告任务本身:消息太长时整理输入;模型接口临时失败时有限重试;根据任务要求选择模型;记录调用耗时。

如果把这些都做成工具,就相当于让模型自己决定“要不要记录日志”“失败以后要不要走系统重试”。很多应用规则不应该依赖它临时想起来。

Middleware 就提供了介入执行过程的位置。LangChain 的概览列出了输入调整、重试、回退、限流等用途。它运行在 Agent 的执行过程里,不是另一个负责业务的模型。

调用前钩子,和“包住调用”有什么区别?

可以先理解两种常见方式。

一种是在某个阶段前后运行。例如调用模型前检查当前消息,按照约定返回状态更新。这类钩子适合处理阶段性的准备和整理。

另一种是包装式钩子。框架会把请求和一个 handler 交进来。handler 可以理解成“继续执行后面的调用”。中间件可以调整请求后继续,也可以根据接口约定返回结果;重试逻辑则可能多次调用后面的处理过程。

Python 和 TypeScript 的命名不同,常见入口可以这样对应:

位置PythonTypeScript
模型调用前before_modelbeforeModel
包装模型调用wrap_model_callwrapModelCall
包装工具调用wrap_tool_callwrapToolCall

这里列的是入口名称,不是两种语言可以直接互换的代码。请求类型、状态更新和返回方式,要分别按照 Python 与 JavaScript 文档处理。

例如,实现模型接口重试时,包装层收到临时错误,再决定是否等待并重试。最终失败还是成功,也由这层按照约定返回。它不只是旁边记了一句“出错了”。

Callback 为什么不能简单当成 Middleware?

Callback 通常是在某个事件发生时得到通知。例如模型开始调用,记录开始时间;调用结束,记录结果和耗时;发生错误,补充错误信息。LangChain 的 Callback 基类定义了这些开始、结束与错误事件入口。

所以,如果只是做 Trace 或统计,Callback 往往就能承担相应职责。没有必要把所有观测代码都迁到中间件里。

但如果要更换这次请求使用的模型,或者明确控制后面的调用是否执行,就应该使用为此设计的接口。不能因为回调拿到了参数,就依靠偷偷修改某个对象、抛异常等方式,假定所有执行器都会按同样方式处理。

两者也不是完全不能做相同的事。Middleware 同样可以记录日志;区别在于需要的是事件通知,还是调用过程的控制能力。选择以后,还要检查日志失败是否会影响主任务,避免一个非必要记录动作把正常回答打断。

多个 Middleware 放在一起,为什么顺序很重要?

假设一层负责记录耗时,另一层负责重试。

如果耗时记录在外层,重试在内层,那么记录的是从第一次尝试到最终结束的整段耗时。中间重试两次,外层可能仍只记一条总记录。

如果重试在外层、记录在内层,那么每次重新调用 handler,都会经过内部记录层,可以得到每一次尝试的时间。

两种都可能有用,但它们回答的不是同一个指标。当前自定义中间件文档明确说明,包装式钩子按嵌套函数调用工作,列表前面的中间件包在外层。

缓存与权限也有类似问题。如果缓存命中后直接返回,而权限检查只放在更内层,就可能没有走到检查。需要根据实际链路安排顺序,并让缓存键包含必要的身份和数据范围,不能只调整列表顺序就当多租户问题全解决了。

测试时可以记录每个钩子的进入和退出,模拟一次失败后成功、一次缓存命中、一次拒绝访问,确认执行次数和跳过路径符合预期。读代码看起来很清楚的嵌套,实际跑一遍往往更容易发现遗漏。

计时放在重试外层记录整个过程,计时放在重试内层分别记录每次尝试,顺序决定观测范围

面试官继续追问

把“失败时重试”写进 Prompt,不就行了吗?

模型可以据此提出再次调用,但这和应用层处理网络超时、调用预算、退避和幂等,不是同一套控制。

尤其工具执行超时,不一定代表没有发生业务动作。退款、发信等写工具不能因为一次异常就无限重跑,要有明确的重复执行保护。

工具调用中间件做了权限检查,工具服务还要再查吗?

要看信任边界,但不能因为某条 Agent 路径检查过,就假定所有入口都会经过它。

工具服务可能还被别的应用或接口调用。真正执行敏感动作的服务,仍应基于可信身份检查权限;中间件可以提前拒绝明显不允许的请求,也可以减少无效调用,但不应成为唯一防线。

只想保存调用耗时,优先选哪一种?

如果现有 Callback 或追踪系统已经能提供需要的事件,用它通常更直接。

如果还要改变请求、控制重试范围,或者统一包装一段调用,再考虑 Middleware。先确定需求,再选插入位置,不是看到新机制就把已有代码全部搬过去。

面试速记卡

  • Tool:完成搜索、查询等具体业务动作。
  • Middleware:介入执行阶段,或包装模型、工具调用。
  • Callback:主要接收事件,方便日志、统计和追踪。
  • 顺序影响:外层包住内层,会改变重试、缓存与检查的执行范围。
  • 安全边界:只控制经过自身的路径,不能代替服务端权限和写操作幂等。
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历