后端转 Agent 面了 8 家挂 7 家:我总结的 Function Call 5 道送命题

“我面了 8 家 Agent 岗,挂了 7 家。每次被追问 Function Call 的时候都挂——我以为我会了,面试官一问我就懵。”

我让他把面试题发过来,一看——全是"看起来会了、实际没到第一层"的送命题。

这篇文章写给谁看

如果你是下面这几类人中的一类,别划走,这篇对你有用:

  • ✅ 后端/前端开发者,想转 AI Agent 岗,薪资至少 +30%
  • ✅ 最近被裁,在面 Agent 岗,简历上写着"熟悉 Function Call / MCP"
  • ✅ 做过几个 Agent 小 Demo,但从来没把 Agent 推到过线上
  • ✅ 面试被问"你的 Agent 怎么防死循环"能答,被问"根因是什么"就卡住

一句话判断:2026 年 Agent 岗位市场的真相是——简历上"会 Function Call"的人很多,面试能撑过 3 轮追问的人不到 10%。送命题不在"是什么",在"为什么这么设计"和"出问题你怎么查"。


为什么这篇非看不可:2026 年 Agent 岗位的冰火两重天

先给你看三组数据,你就知道这个赛道的真实温度:

指标数据来源
AI Agent 岗位同比增长300%Anthropic《2026 智能体编码趋势报告》
Agent 岗位面试通过率18.7%Stanford AI Index 2026
平均薪资(美国对标岗)$185,000Levels.fyi 2026Q1
必备技能数量(平均)7.3 个GitHub Copilot X 调研

📌 以上数据来自素材稿《AI Agent 面试指南:技术问题与评估标准》2026-04-16 采集,写作时用作行业温度参考。

翻译一下:

  • 岗位多(300% 增长)→ 转 Agent 是对的选择
  • 但通过率只有 18.7% → 80% 的人挂在"我以为我会了"
  • 薪资天花板很高 → 进去的人吃肉,进不去的人挨饿

而在所有 Agent 面试题里,Function Calling 是第一道分水岭——答得好能拉开差距,答得差直接挂。


核心认知:你以为的 Function Call,和面试官要的 Function Call

先用一张图,把"看起来会了"和"真的会"的差距给你画出来:

你以为的 Function Call ↓

  用户提问 → LLM "执行"了一个函数 → 返回结果
              (错!LLM 从不执行代码)

面试官要的 Function Call ↓

  定义工具 Schema → LLM 输出调用意图 JSON
                  → 你的代码解析并执行
                  → 结果回传给 LLM(带 tool_call_id!)
                  → LLM 生成最终回答
                  ↑
         每一步都有可能炸

面试官追问的,永远是"每一步可能炸的地方"。

下面 5 道送命题,我按面试官实际追问的层次,从浅到深排好了。每道题都给你 一句话核心观点 + 对比表 + 最小可复现代码 + trade-off 分析——这叫"四件套回答公式",面试现场能直接套用。


送命题 1:LLM 到底"执行"了函数吗?

面试官原话

“你简历上写精通 Function Calling。那我问你——用户说’帮我查下北京天气’,后面发生了什么?”

90% 的人会这么答(❌ 错误版)

“LLM 识别到是天气查询,调用 get_weather('北京') 函数,返回结果给用户。”

这句话里有一个致命错误:LLM 永远不会调用你的函数。

正确答案:四步流程

一句话核心观点:LLM 只输出"我想调什么工具、传什么参数"的结构化 JSON,真正执行的代码 100% 在你自己的应用里跑。

步骤谁来做具体动作
1. 定义工具开发者写 JSON Schema,描述函数名、参数、类型
2. 生成调用意图LLM输出 {"name": "get_weather", "args": {"city": "北京"}}
3. 解析并执行你的代码根据函数名和参数去调真实 API
4. 回传结果开发者把执行结果塞回 messages,交给 LLM 生成最终回答

最小可复现代码(OpenAI 格式)

# Step 1: 定义工具(开发者)
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市的当前天气。仅支持中国大陆城市名。",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "城市名,如'北京'"}
            },
            "required": ["city"]
        }
    }
}]

# Step 2: LLM 生成调用意图(LLM 做,你只是接收)
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "北京天气怎么样"}],
    tools=tools,
)
# LLM 输出:{"name": "get_weather", "args": {"city": "北京"}}
# 注意:LLM 到这里就停了,它不会也不能执行任何代码

# Step 3: 你的代码实际执行(开发者)
tool_call = response.choices[0].message.tool_calls[0]
result = get_weather(json.loads(tool_call.function.arguments))

# Step 4: 回传结果(必须带 tool_call_id!)
messages.append({
    "role": "tool",
    "tool_call_id": tool_call.id,   # 这一行很多人会漏,一漏就 400
    "content": json.dumps(result)
})

trade-off 分析(面试加分点)

为什么要设计成"LLM 不执行代码"?

  • 安全:如果 LLM 能执行任意代码,Prompt Injection 就能直接拿到你服务器的 shell
  • 可控:所有副作用都在你自己的代码里,你能加权限校验、限流、审计日志
  • 可测试:LLM 的输出是纯 JSON,能录制回放做单元测试

面试官想听到这句:“LLM 是决策大脑,不是执行器。这个边界一旦模糊,生产系统必出事故。”


送命题 2:Tool Description 你是写给谁看的?

面试官原话

“我看你 Schema 写得挺漂亮的。这段 description——你是写给人看的,还是写给 LLM 看的?”

为什么这题是送命题

因为Tool Description 是 LLM 选工具的唯一依据。LLM 看不到你的函数实现,也看不到你的注释,它选不选调你这个工具,就看你 description 写得好不好。

对比:人话 vs 给 LLM 的话

# ❌ 写给人看的 description(大多数人会这么写)
"name": "query_order",
"description": "查询订单信息"

# ✅ 写给 LLM 看的 description(面试加分版)
"name": "query_order",
"description": (
    "查询订单当前状态。"
    "【使用场景】用户询问订单发货、物流、退款时使用。"
    "【输入约束】仅接受 9-12 位纯数字订单号,不接受快递单号。"
    "【返回格式】{status, logistics, updated_at}。"
    "【禁止场景】如果用户只说'我的订单'但没给订单号,先用 ask_user 工具追问,不要调本工具。"
)

四件套:Tool Description 的黄金结构

必写字段作用示例
使用场景LLM 判断"该不该选我"“用户询问订单状态时使用”
输入约束LLM 判断"参数格式对不对"“9-12 位纯数字”
返回格式LLM 知道"回来怎么解读"“{status, logistics}”
禁止场景LLM 判断"什么时候别选我"“无订单号时先追问”

代码片段:一个容易被面试官夸的 Schema

tools = [{
    "type": "function",
    "function": {
        "name": "query_order",
        "description": (
            "查询订单状态。仅用于用户明确提供订单号的场景。"
            "返回 {status, logistics, updated_at}。"
            "若用户未提供订单号,先用 ask_user 追问,禁止本工具回填默认值。"
        ),
        "parameters": {
            "type": "object",
            "properties": {
                "order_id": {
                    "type": "string",
                    "pattern": "^[0-9]{9,12}$",   # 用 pattern 直接约束,LLM 会自检
                    "description": "订单号,9-12 位纯数字"
                }
            },
            "required": ["order_id"]
        }
    }
}]

trade-off 分析

写得太详细会不会浪费 token?

  • 会。每个工具的 description 都会塞进 LLM 的上下文
  • 但这是必要成本:description 每省 50 token,工具选错率可能上升 5-10%
  • 解决方案:工具按意图分组,按需加载,不要一次把 50 个工具全塞进去

面试官加分点:“Description 是写给 LLM 看的产品文档,不是写给同事看的注释。”


送命题 3:Parallel Function Call 你用了吗?

面试官原话

“用户说’帮我查下明天北京天气、查下明天的日程、再看看我账户余额’——你的 Agent 是怎么调工具的?”

90% 的人会这么答(❌ 陷阱版)

“一个个调。先调 get_weather,等结果;再调 get_calendar,等结果;最后调 get_balance。”

这答完面试官脸就冷了——因为你暴露了"没跟进过 2024 年之后的进展"。

正确答案:Parallel Function Call

一句话核心观点:GPT-4o / Claude 3.5+ 从 2024 年起就支持一次返回多个 tool_calls,并行执行,延迟从 T1 + T2 + T3 降到 max(T1, T2, T3)

对比:串行 vs 并行

维度串行调用并行调用(Parallel FC)
延迟T1 + T2 + T3max(T1, T2, T3)
Token 消耗3 次对话轮次1 次对话轮次
模型支持所有模型GPT-4o、Claude 3.5+、Gemini 1.5+、DeepSeek V3+
适用场景工具间有依赖(A 的输出是 B 的输入)工具间独立(3 个查询没关系)

📌 事实来源:02-research/2604/ai_agent_interview_guide.md 模块三 Q5,证据等级 A。

代码片段:并行调用的解析循环

import asyncio

response = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    parallel_tool_calls=True,   # OpenAI:默认开,明确写出来更稳
)

# LLM 可能一次返回多个 tool_calls
tool_calls = response.choices[0].message.tool_calls

# 并发执行(asyncio.gather 一把梭)
asyncdefrun(tc):
    func = tool_registry[tc.function.name]
    return tc.id, await func(json.loads(tc.function.arguments))

results = await asyncio.gather(*(run(tc) for tc in tool_calls))

# 结果逐个回传,tool_call_id 必须对上,不然多工具结果会错位
for tc_id, result in results:
    messages.append({
        "role": "tool",
        "tool_call_id": tc_id,
        "content": json.dumps(result)
    })

trade-off 分析

什么时候不能用 Parallel FC?

  • 有依赖关系:比如"先查订单,再根据订单号查物流"——第二个工具的参数依赖第一个的结果,必须串行
  • 有副作用冲突:比如"先扣库存,再下单"——两个写操作并发可能导致数据不一致
  • 不支持的老模型:GPT-3.5、Claude 2 等老模型不支持,硬开会报错

面试官加分点:“识别任务 DAG——独立分支并行,依赖链路串行。这是 Agent 工程的基本功。”


送命题 4:工具报错怎么回传?

面试官原话

“你的 query_order 接口挂了,返回 500。你的 Agent 会怎么反应?”

90% 的人会这么答(❌ 致命版)

“try-except 一下,把错误信息 return 给 LLM。”

这答完——恭喜你,你完美复刻了"Agent 死循环"这个线上 P0 故障的根因。

为什么这样写会死循环

# ❌ 经典错误写法
def query_order(order_id):
    try:
        return db.query(f"SELECT * FROM orders WHERE id={order_id}")
    except Exception as e:
        return f"failed: {e}"

LLM 拿到 "failed: timeout",它怎么想?

“失败了,可能是参数不对。让我换个参数再试试。”

于是它重试,重试,重试……每次重试都烧 token,每次重试都失败,直到触发 max_steps 才停。

正确答案:结构化错误 + 重试信号

一句话核心观点:工具错误不是抛异常,是给 LLM 一封结构化的"故障说明信"——告诉它错在哪、能不能重试、怎么绕过。

代码片段:结构化错误处理

def query_order(order_id):
    try:
        data = db.query_with_timeout(
            f"SELECT * FROM orders WHERE id=%s", (order_id,), timeout=5
        )
        return {"ok": True, "data": data}

    except TimeoutError:
        # 可重试:系统瞬时问题,LLM 稍后重试有意义
        return {
            "ok": False,
            "error_code": "TIMEOUT",
            "retryable": True,
            "retry_after": 5,
            "hint": "数据库超时,建议 5 秒后重试一次,再失败则降级"
        }

    except ValueError as e:
        # 不可重试:输入本身错了,LLM 重试 100 次也没用
        return {
            "ok": False,
            "error_code": "BAD_INPUT",
            "retryable": False,
            "hint": f"订单号格式错误:{e}。请向用户追问正确订单号。"
        }

    except PermissionError:
        # 不可重试 + 提示降级路径
        return {
            "ok": False,
            "error_code": "NO_PERMISSION",
            "retryable": False,
            "hint": "当前用户无权查询该订单,建议引导用户登录或转人工客服"
        }

对比:错误回传的三种写法

写法LLM 的反应后果
❌ 抛异常Agent 直接崩用户体验差
return "failed"当作参数问题,改参数重试死循环 + Token 爆炸
✅ 结构化错误 + retryable + hint基于信号做决策或重试、或追问、或降级

trade-off 分析

为什么要设计 retryablehint 字段?

  • retryable 是给 LLM 的"决策开关":true 时重试,false 时换路径
  • hint 是给 LLM 的"指路牌":不光告诉它错了,还告诉它下一步该干嘛
  • 这种设计叫 “对话式错误”——让工具跟 LLM 像同事一样沟通,而不是甩一个 500 给它

面试官加分点:“能把’工具错误回传’说成’跟 LLM 的一次对话’,说明你对 Agent 的控制流有结构化的认知。”


送命题 5:OpenAI 和 Anthropic 的格式差异

面试官原话

“你们线上用 GPT-4o。如果明天让你切成 Claude 4,代码要改多少地方?”

90% 的人会这么答(❌ 轻敌版)

“改个 API URL 和 SDK 就行。”

错。

正确答案:三层字段差异

维度OpenAIAnthropic Claude
工具定义顶层字段tools + functiontools + input_schema
调用输出字段tool_calls (message 级)tool_use (content block 级)
结果回传 rolerole: "tool"role: "user" + tool_result block
并行调用开关parallel_tool_calls: true默认支持,无显式开关
消息历史格式平铺 messages 数组content 是 block 数组,可混合 text/tool_use

📌 事实来源:02-research/2604/ai_agent_interview_guide.md 模块三 Q5,证据等级 A。写作前若上线请用 web_search 核验两家当前 API 最新字段。

代码片段:同一个工具的两家 Schema

# OpenAI 格式
openai_tool = {
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询城市天气",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"]
        }
    }
}

# Anthropic Claude 格式
anthropic_tool = {
    "name": "get_weather",
    "description": "查询城市天气",
    "input_schema": {              # 注意不是 parameters
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"]
    }
}

结果回传的差异(最容易炸的地方)

# OpenAI:role=tool
messages.append({
    "role": "tool",
    "tool_call_id": "call_abc123",
    "content": json.dumps(result)
})

# Anthropic:role=user + tool_result content block
messages.append({
    "role": "user",
    "content": [{
        "type": "tool_result",
        "tool_use_id": "toolu_abc123",   # 字段名都不一样
        "content": json.dumps(result)
    }]
})

生产解法:中间抽象层(Adapter)

class ToolAdapter:
    """统一工具定义和结果回传,屏蔽不同厂商的格式差异"""

    defto_openai(self, unified_tool: dict) -> dict: ...
    defto_anthropic(self, unified_tool: dict) -> dict: ...
    defto_gemini(self, unified_tool: dict) -> dict: ...

    defwrap_result(self, provider: str, call_id: str, result) -> dict:
        if provider == "openai":
            return {"role": "tool", "tool_call_id": call_id,
                    "content": json.dumps(result)}
        if provider == "anthropic":
            return {"role": "user", "content": [{
                "type": "tool_result", "tool_use_id": call_id,
                "content": json.dumps(result)
            }]}
        # Gemini、DeepSeek 各家自己加...

trade-off 分析

要不要现在就抽 Adapter?

  • 业务还在探索期:先绑一家,别过早抽象
  • 生产系统:必抽。否则切模型那一天,你的代码就是屎山
  • 进阶思路:用 LangChain / LlamaIndex 这类框架,它们已经抽好了。但别忘了——框架抽得有多抽象,你排查 bug 的时候就有多绕

面试官加分点:“切模型最难的不是改代码,是切完之后回归测试——每个 tool_call_id、每个 role、每个 content block 都得跑一遍冒烟。”


送命题之外:3 个附加杀手锏

面试官追问到这里,如果你还能额外甩出这 3 个点,基本稳了。

附加 1:大返回值怎么处理(防 Token 爆炸)

def run_query_tool(sql):
    raw = db.execute(sql)  # 可能几万行
    if token_count(raw) > 2000:
        ref_id = cache.set(raw, ttl=600)
        summary = llm.summarize(raw, max_tokens=500)
        return {
            "summary": summary,
            "reference_id": ref_id,  # LLM 要原始数据时调 fetch_reference(ref_id)
            "total_rows": len(raw)
        }
    return {"data": raw}

关键设计:摘要进上下文,原始数据挂外部。LLM 按需通过 reference_id 拉取,避免一次性塞爆 128K 窗口。

附加 2:工具幻觉怎么防

LLM 偶尔会调一个不存在的工具名(幻觉)。必须做工具名白名单校验:

ALLOWED_TOOLS = {"get_weather", "query_order", "query_balance"}

def execute_tool_call(tool_call):
    if tool_call.function.name not in ALLOWED_TOOLS:
        return {
            "ok": False,
            "error_code": "UNKNOWN_TOOL",
            "retryable": False,
            "hint": f"工具 {tool_call.function.name} 不存在。可用工具:{list(ALLOWED_TOOLS)}"
        }
    ...

附加 3:工具粒度——1 个大工具 vs 3 个小工具

面对"查订单状态"这个需求:

方案工具数LLM 选工具难度参数组合复杂度
1 个大工具(query_order + action 枚举)1容易高 (参数组合爆炸)
3 个小工具(query_order / query_logistics / query_refund)3中 (description 必须写清楚各自场景)

实战判断:

  • 参数组合 ≤ 5 种 → 1 个大工具
  • 参数组合 > 5 种 → 拆成多个小工具
  • 场景完全不同(读 vs 写)→ 一定拆,避免误触发

总结:Function Call 面试通关 6 条核心判断

  • LLM 不执行代码,它只输出调用意图——这是第一道分水岭,过不了这一关,后面全白搭
  • Tool Description 是写给 LLM 看的,不是写给人看的。带使用场景、输入约束、返回格式、禁止场景四件套
  • Parallel Function Call 是送分题——独立任务并发,依赖任务串行,识别任务 DAG 是基本功
  • 工具错误要结构化,带 retryable + error_code + hint。抛异常或 return “failed” 就是给自己挖坑
  • 跨模型的 Schema 差异不是 API URL 的问题——OpenAI 的 role: tool 和 Anthropic 的 tool_result block 是两套玩法
  • 工具不是越多越好——每加一个工具都在跟主任务抢 Token,10 个以内,按需加载

对你的实际建议

🎯 如果你是正在面 Agent 岗的后端/前端:

本文 5 道送命题直接背熟,面试前写一版自己的"四件套回答模板"。重点练"为什么这么设计 + 踩过什么坑"这两层——这是你跟应届生拉开差距的唯一机会。

🎯 如果你还在犹豫要不要转 Agent:

先做一个小 Demo:挑一个你业务里真实的工具(查订单/查库存/查日志都行),用 OpenAI 或 Anthropic 的 API 从零搭一个 Agent,必须跑通错误处理、并行调用、结果回传这三件事。跑通这个流程,面试 Q1-Q5 你就能实打实地讲,而不是背书。

🎯 如果你是招人的 Tech Lead:

用本文 5 道题当筛子。第 1 题"LLM 是否执行代码"刷掉 60%,第 3 题"Parallel FC"刷掉 20%,第 4 题"错误回传"刷掉 10%,剩下的 10% 是真正线上跑过 Agent 的人——18.7% 的通过率就是这么来的。

这里给大家精心整理了一份全面的AI大模型学习资源包括:AI大模型全套学习路线图(从入门到实战)、精品AI大模型学习书籍手册、视频教程、实战学习、面试题等,资料免费分享

👇👇扫码免费领取全部内容👇👇
在这里插入图片描述

1. 成长路线图&学习规划

要学习一门新的技术,作为新手一定要先学习成长路线图方向不对,努力白费

这里,我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。可以说是最科学最系统的学习成长路线。
在这里插入图片描述

2. 大模型经典PDF书籍

书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础(书籍含电子版PDF)

在这里插入图片描述

3. 大模型视频教程

对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识

在这里插入图片描述

4. 2026行业报告

行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

5. 大模型项目实战

学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

在这里插入图片描述

6. 大模型面试题

面试不仅是技术的较量,更需要充分的准备。

在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

在这里插入图片描述

7. 资料领取:全套内容免费抱走,学 AI 不用再找第二份

不管你是 0 基础想入门 AI 大模型,还是有基础想冲刺大厂、了解行业趋势,这份资料都能满足你!
现在只需按照提示操作,就能免费领取:

👇👇扫码免费领取全部内容👇👇
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值