Agent面试真题05:13道工具调用高频面试题,学完淘汰百分之90的人(附详细答案)

四季读书网 2 0
Agent面试真题05:13道工具调用高频面试题,学完淘汰百分之90的人(附详细答案)

大家好,我是大志。

本篇文章我结合自己的学习和项目经验,把工具调用相关的高频面试题整理成了一份比较完整的总结。不仅会介绍每个知识点是什么,还会结合实际开发场景,聊聊生产环境中常见的设计思路,希望能够帮助大家建立更加系统的知识体系。

另外,完整的 AI Agent 面试题文档也已经同步到了 aiflowline.cn,大家可以结合文章一起阅读。

1、什么是Function Calling

Function Calling(函数调用)是LLM所具备的一种能力,函数调用是指模型不仅可以生成文本回答,还可以根据用户的提问,判断是否需要使用提供的工具,如需工具,就自动生成结构化参数,将工具调用信息返回给Agent程序,让Agent程序去调用外部工具(如外部API、数据库、搜索引擎等),最终将工具调用结果再返回给LLM来,LLM根据工具调用结果来判断是否还需要继续调用工具,还是直接返回结果。

Agent面试真题05:13道工具调用高频面试题,学完淘汰百分之90的人(附详细答案)-第1张图片-四季读书网
工具调用流程

工具调用的核心思想是让大模型不只是能回答问题,还能根据问题和提供的工具来决定要不要调用工具、调用哪个工具,以及如何传参,从而完成更多的任务。

例如用户问“上海今天的天气怎么样”,LLM的训练数据肯定不包括这类实时信息,LLM可能会直接编造答案,但是有了工具调用,LLM就会判断需要调用如get_weather工具,并生成类似 get_weather(city="上海") 这样的函数调用参数,返回给Agent执行工具。

Function Calling 的本质,其实是大模型与外部系统之间的一种结构化通信方式,让 LLM 从聊天机器人变成”可以使用工具的Agent智能体”,通过工具调用可以解决LLM无法获取实时信息、无法执行外部操作的问题。

2、如何设计Tool Schema

Tool Schema 的核心目标是LLM能够准确理解工具的用途、用法,并正确生成调用参数。因此,一个好的Tool Schema不仅是给程序看的,更重要的是给LLM看的。

在设计 Tool Schema 时,最重要的是写好 namedescription 和 parameters。其中模型最依赖的其实是 description,因为模型并不知道我们的代码逻辑,它只能通过描述来理解这个工具是干什么的。除此之外,参数的设计也要尽量清晰、具体,避免让模型猜测。

在设计工具和Tool Schema时,要遵循单一职责原则,一个 Tool 最好只完成一类任务。

例如:

get_weather(city)search_news(keyword)

而不是做一个万能工具,这会增加模型选择和参数生成的难度,降低调用成功率。

query_data(    type,    city,    keyword,    category,    ...)

工具名称也应该尽可能清晰明了,如果多个工具功能相似,需要在 Description 中明确区分边界。

例如:search_websearch_internal_docs,描述中要明确说明:一个搜索互联网,一个搜索企业知识库,否则LLM容易选错工具。

3、工具调用如何做参数校验?

参数校验的目的是确保 Agent 生成的工具调用参数符合要求,避免因为参数错误导致工具调用失败。

虽然 Function Calling 会根据 Tool Schema 生成参数,但大模型并不能保证生成的参数一定正确。例如用户输入异常、模型理解错误或者参数缺失时,都可能导致工具调用失败。因此在工具真正执行之前,必须进行参数校验。

LangChain为例,通常会使用 Pydantic 定义参数模型:

from pydantic import BaseModel, FieldclassWeatherInput(BaseModel):    city: str = Field(        description="需要查询天气的城市名称"    )

然后绑定到 Tool

@tool(args_schema=WeatherInput)defget_weather(city: str):returnf"{city}天气晴朗"

当模型生成参数后:

{"city""北京"}

会先经过 Pydantic 校验,通过后才会执行工具。

如果模型生成:

{"city"123}

或者:

{}

则会抛出异常,不会继续执行工具。除了类型校验之外,实际项目中还经常会做业务规则校验。例如:

from pydantic import field_validatorclassWeatherInput(BaseModel):    city: str    @field_validator("city")    @classmethoddefvalidate_city(cls, value):ifnot value.strip():raise ValueError("城市不能为空")return value

这样即使模型生成的city参数是空字符串,参数也会校验不通过。

4、工具调用失败如何处理?

在 Agent 应用中,工具调用失败非常常见,例如网络超时、API 限流、参数错误、数据库异常等。因此,工具调用需要设计完善的异常处理机制。

首先,要在 Tool 内部捕获异常,避免因为一个工具执行失败导致整个执行流程崩溃,让Agent仍然能够获取错误信息并继续运行。

@tooldefget_weather(city: str):try:return weather_api.query(city)except Exception as e:returnf"天气查询失败:{str(e)}"

对于临时性故障,例如网络抖动、接口超时等问题,可以增加重试机制。

from tenacity import retry@retry(stop=stop_after_attempt(3))defget_weather(city: str):    ...

Agent应用中,更推荐把错误信息返回给模型,而不是直接终止流程,模型收到错误后可以自主决策,比如返回给用户:天气查询服务暂时不可用,请稍后重试。

在复杂工作流中,还可以设计降级(Fallback)策略,例如主搜索工具Google Serper失败时,采用Bing Search继续进行搜索。

在生产环境中,还需要记录日志和监控,记录调用了哪个工具、输入参数是什么、错误原因是什么、重试次数是多少,这样方便后续排查问题和优化。

5、如何设计超时机制?

在 Agent 应用中,工具调用通常依赖第三方 API、数据库或搜索服务,这些服务可能出现请求超时的情况。因此需要设计超时机制,避免某个工具调用超时阻塞整个 Agent 流程。

最简单的做法是在调用外部服务时设置请求超时时间。例如使用 requests 调用接口时:

response = requests.get(    url,    timeout=10)

如果 10 秒内没有返回结果,则抛出超时异常,交给模型去处理。

@tooldefget_weather(city: str):try:return weather_api.query(city)except TimeoutError:return"天气服务请求超时"

模型获取到错误信息,并决定后续如何处理。

在一些复杂场景下,经常会使用降级策略。当主工具超时时,可以切换到备用工具,这样能够提升整体成功率。

6、如何设计重试机制?

在 Agent 应用中,工具调用失败并不一定意味着真正的业务失败,很多时候是因为网络抖动、接口超时、服务限流等原因。因此通常会设计重试机制,在失败后自动重新执行,来提高工具调用成功率。

最简单的方式是在 Tool 调用失败后重试固定次数。例如:

from tenacity import retry, stop_after_attempt@retry(stop=stop_after_attempt(3))defget_weather(city: str):return weather_api.query(city)

当调用失败时,系统会自动重试,最多执行 3 次。

实际项目中,更推荐使用指数退避(Exponential Backoff策略,而不是立即重试。

例如:

第1次失败 → 等待1秒第2次失败 → 等待2秒第3次失败 → 等待4秒

这样可以避免在服务异常时持续发送大量请求,增加系统压力。而且,并不是所有错误都适合重试,需要区分错误类型。对于网络超时、服务暂时不可用、API 限流(429)、数据库连接失败可以尝试重试。

而对于参数错误、权限不足、资源不存在、数据格式错误这类错误,无需进行重试,大概率重试之后也不会成功。

并且重试也要控制重试次数,如最大重试次数3~5次,超过次数后直接返回错误信息,否则可能陷入死循环。

7、什么是指数退避重试?

指数退避(Exponential Backoff)是一种重试策略,每次重试的等待时间呈指数增长。

如:

第1次重试:等待 1秒第2次重试:等待 2秒第3次重试:等待 4秒第4次重试:等待 8秒第5次重试:等待 16秒

等待时间 = 基础等待时间 * 2^(重试次数-1)

那么,为什么需要指数退避呢?如果服务端出了问题,大量客户端同时重试会导致服务端压力非常大,指数退避让客户端的重试间隔越来越长,能给服务端恢复的时间。

具体实现如下:

import timeimport randomdefexecute_with_backoff(tool_name, arguments, max_retries=5, base_delay=1):for attempt in range(max_retries):try:return execute_tool(tool_name, arguments)except RETRYABLE_ERRORS as e:if attempt == max_retries - 1:raise# 指数退避 + 随机抖动            delay = base_delay * (2 ** attempt) + random.uniform(01)            time.sleep(delay)

纯指数退避可能导致多个客户端在同一时刻重试,加入0-1秒的随机抖动(Jitter)可以错开重试时间:

纯指数退避:1s, 2s, 4s, 8s(所有客户端相同)加入抖动后:1.3s, 2.1s, 4.7s, 8.2s(每个客户端根据随机抖动不同)

8、如何设计Fallback机制?

Fallback是指当前工具不可用时,Agent自动切换到备选工具。

  • 工具降级

同一个功能准备多个备选工具:

TOOL_FALLBACK = {"search_web": ["search_web_v2""search_bing"],"get_weather": ["get_weather_api2"],}defexecute_with_fallback(tool_name, arguments):    tools = [tool_name] + TOOL_FALLBACK.get(tool_name, [])for tool in tools:try:return execute_tool(tool, arguments)except Exception as e:            logger.warning(f"工具{tool}调用失败,尝试其他工具: {e}")continueraise Exception(f"所有备选工具都失败: {tools}")

这里要注意首选工具和备选工具参数和用法可能不完全相同,在发生降级调用时,要处理好这部分的逻辑。

  • 策略级Fallback

当工具调用全部失败时,降级到不需要工具的方案:

正常流程:用户问天气 → 调用天气API → 返回实时天气 降级流程:天气API调用失败 → 返回给用户"当前无法获取实时天气,建议您查看天气App"

  • 缓存Fallback

当调用工具失败之后,尝试使用缓存数据:

defexecute_with_cache_fallback(tool_name, arguments):# 先尝试实时调用try:        result = execute_tool(tool_name, arguments)        cache.set(tool_name, arguments, result, ttl=300)  # 缓存5分钟return resultexcept Exception:# 实时调用失败,尝试从缓存获取        cached = cache.get(tool_name, arguments)if cached:return {"data": cached, "source""cache""warning""使用了缓存数据"}raise

使用缓存进行降级处理时,要结合具体业务场景,有些信息是必须要实时获取才有意义,避免获取到过期的信息。

9、如何避免Agent误调用工具?

Agent有时候会在不需要调用工具时调用工具,或者调用错误的工具。

  • Prompt中明确工具使用场景
工具使用规则:- 只有当用户明确要求执行操作时才调用工具- 如果用户只是在闲聊或问概念性问题,直接回答,不要调用工具- 不确定是否需要调用工具时,优先不调用
  • 高风险的工具调用进行人工确认

如下示例使用LangGraph来进行工具调用确认:

from typing import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.types import interrupt, Commandfrom langgraph.checkpoint.memory import InMemorySaverclassState(TypedDict):    result: str# 高风险工具defdelete_database():return"数据库已删除"# 人工确认defhuman_review(state: State):    approved = interrupt("是否允许删除数据库?")return {"approved": approved}# 根据审批结果路由defrouter(state):return"tool"if state["approved"else"reject"# 执行工具deftool(state):return {"result": delete_database()}# 拒绝执行defreject(state):return {"result""已取消操作"}builder = StateGraph(State)builder.add_node("review", human_review)builder.add_node("tool", tool)builder.add_node("reject", reject)builder.add_edge(START, "review")builder.add_conditional_edges("review", router)builder.add_edge("tool", END)builder.add_edge("reject", END)graph = builder.compile(checkpointer=InMemorySaver())

人工确认

graph.invoke(Command(resume=True), config=config)
  • 工具描述中写明边界

如在编写发送邮件send_email时,添加描述:发送邮件。仅在用户明确要求发送邮件时调用,不要在用户只是询问邮件相关问题时调用。

  • 减少工具数量

工具越多,LLM选错的概率越大。定期清理不常用的工具,保持工具列表精简。

10、如何限制Agent调用危险工具?

某些工具(如删除数据、执行代码、发送邮件)具有不可逆的影响,需要严格限制。

  • 权限分级
TOOL_RISK_LEVEL = {"search_web""low",       # 只读操作,风险低"send_email""medium",    # 有副作用,中等风险"delete_file""high",     # 不可逆,高风险"execute_code""critical"# 可能造成严重后果}defcheck_tool_permission(tool_name, user_role):    risk = TOOL_RISK_LEVEL.get(tool_name, "unknown")if risk == "critical":return user_role == "admin"if risk == "high":return user_role in ["admin""operator"]returnTrue
  • 二次确认

高风险操作必须经过人工确认,具体示例如上面所示的删除数据二次确认的案例。

  • 日志审计

记录所有高风险工具的调用日志,方便排查问题和回滚。

11、工具执行异常如何回滚?

当工具执行到一半失败了,是否需要回滚,需要根据工具具体进行判断,查询类工具通常不需要回滚;而创建订单、扣库存、删除数据等写操作,如果后续流程失败,就应该考虑补偿机制。

由于 Agent 会跨数据库、HTTP API、第三方服务等多个系统,通常无法依赖传统数据库事务,而是采用 Saga 或补偿事务模式,为每个关键工具设计对应的回滚操作。

  • 事务性设计

尽量让工具调用支持事务,失败时自动回滚:

defexecute_with_transaction(operations):    executed = []try:for op in operations:            result = execute_tool(op["tool"], op["arguments"])            executed.append({"tool": op["tool"], "result": result})return {"success"True"results": executed}except Exception as e:# 逆序回滚已成功执行的操作for op in reversed(executed):            rollback(op["tool"], op["result"])return {"success"False"error": str(e)}
  • 补偿操作

如果不能回滚,设计补偿操作来撤销影响:

COMPENSATION_MAP = {"send_email""send_recall_email","create_order""cancel_order","transfer_money""reverse_transfer",}defcompensate(tool_name, result):    compensation_tool = COMPENSATION_MAP.get(tool_name)if compensation_tool:        execute_tool(compensation_tool, {"original_result": result})
  • 标记待确认

对于无法回滚的操作,先标记为"待确认"状态,等待后续处理。

defexecute_with_pending(tool_name, arguments):    result = execute_tool(tool_name, arguments)# 标记为待确认状态    mark_as_pending(result["id"])return resultdefconfirm_execution(result_id):    mark_as_confirmed(result_id)

12、如何记录工具调用日志?

完善的日志记录是排查问题和优化Agent的基础。

(1)日志内容

每次工具调用需要记录以下信息:

deflog_tool_call(tool_name, arguments, result, duration, error=None):    log_entry = {"timestamp": datetime.now().isoformat(),"tool_name": tool_name,"arguments": arguments,"result_summary": str(result)[:200],  # 只记录摘要,避免日志过大"duration_ms": duration,"success": error isNone,"error": str(error) if error elseNone,"session_id": get_session_id(),"user_id": get_user_id()    }    logger.info(json.dumps(log_entry, ensure_ascii=False))

(2)敏感信息脱敏

日志中可能包含敏感信息,需要脱敏处理:

defmask_sensitive(arguments):    masked = arguments.copy()    sensitive_keys = ["password""token""card_number""id_card"]for key in sensitive_keys:if key in masked:            masked[key] = "***"return masked

(3)日志分级

# 普通工具调用 → INFO级别logger.info(f"工具调用成功: {tool_name}")# 工具调用失败 → WARNING级别logger.warning(f"工具调用失败: {tool_name}, 错误: {error}")# 高风险工具调用 → 单独记录审计日志audit_logger.info(f"高风险操作: {tool_name}, 用户: {user_id}")

13、如何追踪工具调用链路?

一个Agent任务可能包含多次工具调用,需要把它们串联起来形成完整的调用链路。

(1)Trace ID

给每个Agent任务分配一个唯一的Trace ID,所有相关的工具调用都带上这个ID

import uuidclassToolCallTracer:def__init__(self):        self.trace_id = str(uuid.uuid4())        self.spans = []defstart_span(self, tool_name, arguments):        span = {"trace_id": self.trace_id,"span_id": str(uuid.uuid4()),"tool_name": tool_name,"arguments": arguments,"start_time": time.time(),"status""running"        }        self.spans.append(span)return span["span_id"]defend_span(self, span_id, result=None, error=None):for span in self.spans:if span["span_id"] == span_id:                span["end_time"] = time.time()                span["duration"] = span["end_time"] - span["start_time"]                span["result"] = result                span["error"] = error                span["status"] = "success"if error isNoneelse"error"break

(2)调用链路可视化

把追踪数据可视化,方便排查问题:

Trace: abc-123├── [0.0s] search_web(query="AI新闻")│   └── [2.3s] 成功,返回10条结果├── [2.5s] summarize(text=搜索结果)│   └── [5.1s] 成功,生成摘要└── [5.3s] send_email(to="team@example.com", content=摘要)    └── [6.8s] 成功,邮件已发送总耗时:6.8

(3)关联LLM调用

除了工具调用,LLM的调用也需要追踪,形成完整的调用链路:

完整调用链路:├── LLM调用 #1 → 决定调用search_web├── Tool调用 search_web → 返回搜索结果├── LLM调用 #2 → 决定调用summarize├── Tool调用 summarize → 返回摘要├── LLM调用 #3 → 决定调用send_email├── Tool调用 send_email → 发送成功└── LLM调用 #4 → 生成最终回答总耗时:20.3

好啦,今天这期 工具调用 面试题就到这里。后面我会每周至少更新1期面试题系列,想看后续 AI Agent 进阶面试题】 的朋友,欢迎关注「大志说编程」!

觉得有用的话,转发给正在面试的小伙伴,咱们下期见~

往期回顾

Agent面试真题01:这15道Agent基础题,卡掉了80%的候选人!

Agent面试真题02:一次讲透面试官最爱问的10个Agent架构问题

Agent面试真题03:突击LangChain面试!13个核心问题,背完直接去面大厂

Agent面试真题04:10道LangGraph高频面试题,90% AI Agent面试都会问!(附详细答案)

抱歉,评论功能暂时关闭!