面灵AI→

拼多多Agent方向一面面经(60min双机位)

轮次
一面
时间
2026-09
来源
牛客网

《面试题目》

  1. 模型调用工具是单次单个还是能调用多个?多个是并行还是串行?
  2. 如果让你设计一个有工具调用的 Agent,你会怎么选择?
  3. 上下文压缩的时候怎么压缩?哪些内容是可以被放弃的?
  4. 上下文压缩后怎么保证缓存命中?
  5. 模型调用工具的时候怎么保证不出冲突?
  6. 大量工具需要调用的情况下出现准确率低,怎么解决?
  7. 项目相关:怎么解决的文档解析出错的问题?
  8. 项目的数据来源是什么?
  9. 项目的目的/业务价值是什么?
  10. 为什么用 Agent(而不用传统方案)?
  11. 代码题:括号生成。
  12. 反问:对 Jev 怎么看(原文如此,疑为笔误)?古法编程和 Agent 编程怎么平衡?

《参考解析》

多个工具调用的并行与串行:主流模型(OpenAI、Anthropic、Qwen 等)都支持在一次 assistant 回复里返回多个 tool_calls,服务端可以判断这些调用之间有没有依赖:无依赖的只读工具(查天气、查库存、检索文档)可以并行执行,然后把多个 tool_result 按各自的 tool_call_id 一起回填到下一轮上下文,延迟等于最慢的那个而不是求和;有数据依赖的(后一个参数要用前一个的结果)必须串行;有副作用的(下单、发消息、写库)即使互不依赖也建议串行或加幂等键,因为并发写的顺序不可控。工程上还要配并发上限和超时(打满下游限流会雪崩)、失败重试与部分失败处理(一个工具失败时是把错误回灌给模型还是整体回滚,要在 prompt 里写清规则)。

怎么设计一个带工具调用的 Agent:几个关键决策点——①工具数量:一次注入模型的工具最好控制在 20 个以内,太多必然选错,超过就做分层(先选工具组/命名空间,再选具体工具)或做工具检索(用 embedding/BM25 先召回 Top-K 相关的,而不是全塞);②工具描述就是 prompt:名字、description、参数说明都要写清”什么时候该用、什么时候不该用、参数含义与取值范围”,这是提升准确率性价比最高的手段;③参数用 JSON Schema 强约束,能枚举就枚举,能给默认值就给默认值,减少模型自由发挥;④返回值要裁剪:工具结果只回填模型决策需要的字段,原始大结果(长文档、全量列表)存到外部存储,给模型一个引用 id,否则上下文会瞬间爆炸;⑤错误处理:把结构化错误(不是堆栈)回灌给模型让它自我纠错,非致命错误不要中断循环;⑥可观测:每一步记录工具名、参数、耗时、token 消耗,便于回溯”它为什么选错了”;⑦兜底:模型无法决策或置信度低时反问用户,而不是硬猜。

上下文压缩:先分清哪些能丢。不能丢的:system prompt 与工具定义(决策依据)、当前任务的目标与约束、最近的若干轮对话、模型已经产出的关键中间结论(ID、数值、文件名)。可以压的:早期的寒暄与试错过程、已经被摘要吸收的原始长文本、大工具的完整返回。压缩手段由轻到重:①截断——只保留最近 N 轮,简单但会丢早期约束;②滚动摘要——用一次 LLM 调用把旧对话摘要成结构化要点(保留实体、ID、待办),原文丢弃;③工具结果结构化压缩——长文档切片存外部,上下文里只留摘要 + 可检索句柄;④分层记忆:工作记忆(当前轮上下文)/ 会话记忆(滚动摘要)/ 长期记忆(向量库或笔记文件,按关键词或向量按需召回)。一个必须遵守的硬约束:tool_call 与其对应的 tool_result 必须成对保留或成对删除,只删一半会被 API 直接拒绝。

压缩后怎么保证缓存命中:大模型的 prompt caching(OpenAI 的自动前缀缓存、Anthropic 的显式 cache_control 断点)命中的条件是前缀逐 token 完全一致。由此推出几条设计纪律:①把稳定内容放前面(system prompt、工具定义、few-shot 示例),把易变内容(时间戳、随机 id、每轮新增的摘要)放后面;②压缩时只动尾部,绝不要把新生成的摘要插到 system prompt 之前,那样整个前缀失效、缓存全灭;③对话历史尽量 append-only,不要回写重写历史消息;④把缓存断点打在稳定段的末尾并保持多轮一致;⑤上线后监控 cache_read_input_tokens 与 cache_creation_input_tokens 的比例来验证命中率,命中率掉了优先怀疑是不是有人往 prompt 头部塞了动态内容。

工具调用冲突怎么保证不出错:冲突分三类——并发写同一资源(两个工具同时改一条订单)、顺序依赖被并行打乱、同一操作被重复执行(模型重试导致重复下单)。对应解法:①有副作用的工具强制串行执行;②工具层做幂等,把 idempotency_key 作为参数,服务端按 key 去重;③对共享资源加锁或用乐观锁(version/CAS),冲突时返回明确的错误码;④写操作做成”预检—提交”两阶段,先把要改的东西校验一遍再落库;⑤prompt 里明确告诉模型哪些工具可以同时调、哪些必须先调 A 再调 B。真发生冲突时要返回可读的结构化错误,让模型能重新规划,而不是抛一个 500 让它瞎猜。

工具很多时准确率下降怎么办:按投入产出排序:①工具检索(RAG over tools)——按用户 query 召回最相关的 5~10 个工具再注入,这是最立竿见影的;②分层收敛——先分类到工具组,再在组内选;③合并与瘦身——把功能相近的工具合并,砍掉参数(一个工具 3 个参数比 8 个参数准得多),参数尽量用枚举;④命名规范化——用一致的动词-名词风格,避免语义重叠的名字;⑤few-shot 路由示例,特别是展示容易混淆的工具该怎么选;⑥约束解码/小模型路由,用一个小模型专门做工具选择,或者用 grammar/结构化输出限制候选;⑦校验 + 回灌,参数不合法就报错让它重试;⑧训练侧可以构造 hard negative 样本做微调。定位问题时先看 trace:是”没选对工具”还是”参数填错”,两种病因的解法完全不同。

代码题:括号生成(n 对括号的所有合法组合):回溯 + 剪枝,两个约束——左括号数量 < n 时可以放 (;右括号数量 < 左括号数量 时可以放 )。

def generate(n: int) -> list[str]:
    res, path = [], []
    def dfs(left: int, right: int) -> None:
        if len(path) == 2 * n:
            res.append("".join(path))
            return
        if left < n:
            path.append("("); dfs(left + 1, right); path.pop()
        if right < left:
            path.append(")"); dfs(left, right + 1); path.pop()
    dfs(0, 0)
    return res

复杂度正比于答案个数(卡特兰数 C(2n,n)/(n+1)),剪枝保证了不会生成非法串。面试官常追问的两点:①为什么剪枝条件是这样——任何时刻已放置的右括号都不能超过左括号,这是合法括号序列的充要条件;②不用回溯怎么做——可以用 DP:dp[i] 表示 i 对括号的所有组合,dp[i] = "(" + dp[j] + ")" + dp[i-1-j](j 从 0 到 i-1),或者用 BFS 逐层扩展。