想象一下,一位博学多才的“幕僚长”坐在办公室里,他读过成千上万本书,能回答任何理论问题,但他的手不能提、肩不能扛,连开个门都办不到。老板让他订机票、查天气、发邮件,他只能干瞪眼——因为他只会“说话”,不会“做事”。现在,公司给他配了一本《服务手册》,上面写着每个部门能干什么、需要什么材料。幕僚长只需要填一张“工单”,交给对应的执行部门,再把结果带回来汇报给老板。这套机制落到大模型身上,就是“工具调用”(Tool Calling):大模型不亲自执行动作,而是输出一个结构化的“调用请求”,由你的代码去调用真实世界的 API 或函数,再把结果喂回给模型。这就是 Agent(智能体)从“会聊天”走向“能办事”的关键一跃。本文就用“幕僚长 + 服务手册 + 工单流转”的故事,带你彻底看懂工具调用的来龙去脉。

岗位关联

对 AI 从业者而言,掌握工具调用是构建 Agent 的必修课:它决定了模型能否接入真实系统、执行有实际价值的任务,是对话式 AI 走向生产力的核心能力。

目录
  1. 可视化演示
  2. 工具调用本质
  3. 工具定义规范
  4. 调用流程流转
  5. 结构化参数构造
  6. 结果解析融合
  7. 多工具编排策略
  8. 开放挑战与标准化
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示LLM工具调用全流程(user→LLM→JSON→API→resp含结果回环)、自然语言到结构化参数的映射构造、以及多工具选择编排策略与评分排序。

本节配有交互动效(进入视口后自动加载,约 3KB)。

工具调用五阶段流水线 用户查询(user)经LLM推理输出结构化函数调用(call),包含函数名name和参数args的JSON 外部执行环境接收JSON调用真实API,结果(rslt)回传LLM生成最终响应(resp) resp=LLM(query,API(call(query))) 工具调用让LLM从纯文本推理走向可操作真实世界的智能体

工具调用本质

我们的故事从幕僚长说起。他知道天文地理,能写一手漂亮的奏折,但老板要他把文件送到隔壁大楼,他只能尴尬地站在原地——因为他只是一台“会说话的机器”。幕僚长缺的不是智慧,而是“手”。给 LLM(大语言模型)装上工具调用,就是给它配了一双可以借来的手。

LLM 本身只会做一件事:根据输入的 token 预测下一个 token,最终生成文本。这意味着它无法真正查询数据库、调用支付接口、控制机器人。工具调用的本质,是让模型在文本输出里夹带一份“结构化请求”:我要调用哪个工具、传入什么参数。真正动手执行的,不是模型,而是你写的宿主程序。这种“意图与执行分离”的设计,让模型从“知道”走向“做到”,也让 Agent 第一次有能力影响真实世界。

在工程实现上,模型会输出类似 tool_calls 的字段,其中包含工具名和 arguments(参数)。宿主程序看到这个字段后,就去调用对应的函数,拿到返回值,再把这个返回值以系统消息的形式放回对话里。于是模型可以“看到”结果,并据此给出最终回答。整个过程可以简洁地表示成一个三元组:

类比到幕僚长的故事里:他不需要亲自去送文件,只需要在工单上写下“送往哪个部门、交给谁”,自然有跑腿专员完成后面的事。工具调用就是把这套“只写工单、不亲自动手”的智慧,变成了 AI 的默认能力。

call≜(f,x);r=f(x)\displaystyle \text{call} \triangleq (f, \mathbf{x}); \quad r = f(\mathbf{x})
通俗地说,一次工具调用就是“选哪个函数(f)、传什么参数(x)、执行后得到什么结果(r)”。
  1. 选工具模型先从工具清单里选出最合适的函数名,记为 f。
    f∈Tf \in \mathcal{T}
  2. 填参数模型按照工具的 Schema 生成参数向量 x,就像填写工单的各个字段。
    x=JSON arguments\mathbf{x} = \text{JSON arguments}
  3. 执行并返回宿主程序调用函数,得到结果 r,再把它交给模型继续推理。
    r=f(x)r = f(\mathbf{x})

工具定义规范

工单要能流转,前提是有一本大家都能看懂的《服务手册》。幕僚长之所以敢填单,是因为手册上每项服务都写清楚了三件事:服务叫什么、负责做什么、需要客人提供什么材料。工具定义规范,就是给模型看的服务手册。

在代码里,一个工具通常被定义成一个 JSON 对象,包含三个核心字段:name(函数名)、description(自然语言描述)、parameters(参数 Schema)。name 是唯一标识,description 告诉模型这个工具什么时候该用,parameters 用 JSON Schema 描述参数结构。描述写得越清楚,模型越不容易把“查天气”和“查日历”搞混。

工具集合可以形式化地写成一组三元组:

就像服务手册不允许手写模糊词,工具的 description 也必须精确。公司的部门越来越多,手册越规范,幕僚长出错越少;同理,工具定义越规范,大模型调用工具的准确率就越高。

T={(ni,di,Si)}i=1M\displaystyle \mathcal{T} = \{ (n_i, d_i, S_i) \}_{i=1}^{M}
意思是:系统里一共有 M 个工具,每个工具都由“名字 n、描述 d、参数 Schema S”三件套组成。
  1. 集合表示花括号表示这是一个工具集合,i 从 1 取到 M,代表 M 个候选工具。
    T={⋯ }i=1M\mathcal{T} = \{ \cdots \}_{i=1}^{M}
  2. 三要素每个工具是一个三元组,n 是函数名,d 是给模型看的描述,S 是参数约束。
    (ni,di,Si)(n_i, d_i, S_i)
  3. 约束能力模型看到 S 后,才知道参数该传字符串还是数字、哪些必填。
    Si⊨JSON SchemaS_i \models \text{JSON Schema}

调用流程流转

有了手册和工单,接下来就是标准的办公室流程:幕僚长写好工单 → 交给跑腿专员 → 专员去执行部门办事 → 拿到回执 → 回来向幕僚长汇报 → 幕僚长再向老板汇报。工具调用的完整流程,几乎一模一样。

一个标准流程通常是:用户提问 → 模型判断需要工具,输出 tool_calls → 宿主程序解析并调用真实函数 → 工具返回结果 → 结果被拼回对话 → 模型基于结果生成最终回答。如果信息还不够,模型可以再发起一轮工具调用,形成多轮迭代。这个循环会一直持续,直到模型不再输出 tool_calls。

从状态角度看,每一轮对话都可以看作一个状态 s,工具结果 r 会更新这个状态:

这套流程的价值在于“可复现、可追踪”。幕僚长每一次派单都有记录,老板可以随时查看;工程上每一次工具调用也都有日志,方便调试和审计。工单流转的严谨性,正是工具调用流程能够成为 Agent 标准动作的原因。

st+1=M(st,rt)\displaystyle s_{t+1} = \mathcal{M}(s_t, r_t)
模型 M 把当前状态和工具返回结果合并,得到一个新状态,用于生成下一轮输出。
  1. 当前状态s_t 表示第 t 轮时,对话历史、用户问题、之前工具结果组成的上下文。
    st=contextts_t = \text{context}_t
  2. 工具结果r_t 是刚执行完的工具返回值,它会被序列化成文本加入上下文。
    rt=result of toolr_t = \text{result of tool}
  3. 状态更新模型 M 读取新上下文,决定是再次调用工具还是输出最终答案。
    st+1=M(st,rt)s_{t+1} = \mathcal{M}(s_t, r_t)

结构化参数构造

幕僚长填工单时,最怕什么?最怕申请表上写着“姓名(必填):”,他却填了个数字;写着“年龄(数字):”,他填了个“年轻”。参数填错,服务部门就没法干活。工具调用里的结构化参数构造,就是确保这张表不被填错。

JSON Schema 就是这张表的“填写规范”。它定义了每个参数的类型(string、number、object 等)、是否必填、取值范围、嵌套结构。模型在生成 arguments 时,必须遵循这套规范;宿主程序在调用前也可以做一次校验。一个合法的参数对象必须满足所有约束条件:

一旦参数出错,轻则工具报错,重则产生错误的业务动作。现实中,优秀的 Agent 框架会在调用工具前增加一层参数校验器,就像办公室前台先检查工单格式再盖章。毕竟,幕僚长填错的代价,不能由执行部门承担。

∀k∈R:k∈x∧type(xk)=τk\displaystyle \forall k \in R: k \in \mathbf{x} \wedge \text{type}(x_k) = \tau_k
公式想表达:对必填参数集合 R 中的每一个参数 k,它都必须出现在参数对象 x 中,且类型要符合规定 τ。
  1. 必填集合R 是所有必填参数的集合,比如查询天气时 city 必填。
    R={city}R = \{\text{city}\}
  2. 存在性检查k ∈ x 表示参数对象里必须真的有这个键,缺了就会校验失败。
    key exists in arguments\text{key exists in arguments}
  3. 类型检查type(x_k) = τ_k 表示值的类型必须和 Schema 声明的一致。
    typeof value matches schema\text{typeof value matches schema}

结果解析融合

专员办事回来,不会把一摞原始单据直接甩给老板,而是先回到幕僚长这里,把单据翻译成老板能听懂的汇报。工具调用的结果,同样需要被“翻译”一遍才能发挥价值。

工具返回的通常是一段 JSON 字符串。宿主程序要把它解析成结构化对象,判断是正常结果还是错误信息,再序列化成一段文本消息,放回模型的上下文中。这样模型才能“读懂”结果并组织自然语言回复。如果解析失败或结果异常,不能默默吞掉,而要把错误信息原样回传给模型,让它自己纠错或换一个工具。上下文更新可以写作:

这一步最容易被忽略。很多初学者以为工具返回了 JSON,模型就会自动理解;实际上,如果不把结果拼接回对话,模型就还是“看不见”结果。幕僚长要做的不是把单据贴在脸上,而是把关键信息提炼进自己的汇报里,这才是结果解析融合的精髓。

ht+1=ht⊕serialize(rt)\displaystyle h_{t+1} = h_t \oplus \text{serialize}(r_t)
含义:新上下文 = 旧上下文 + 工具返回结果序列化后的文本。
  1. 旧上下文h_t 是模型当前已知的全部信息,包括用户问题、历史轮次。
    ht=historyh_t = \text{history}
  2. 序列化结果serialize(r_t) 把工具返回的 JSON 变成一行或一段可读文本。
    serialize(rt)=string\text{serialize}(r_t) = \text{string}
  3. 拼接融合⊕ 表示拼接,新上下文把旧历史和工具结果放在一起。
    ht+1=ht⊕stringh_{t+1} = h_t \oplus \text{string}

多工具编排策略

一个复杂的任务,往往不是单个部门能完成的。比如老板说:“帮我订明天去上海的机票,再订一家离机场近的酒店。”幕僚长需要同时联系航空公司订票、联系酒店订房;但如果酒店想派接送机,就必须等航班号出来才能订。这就是多工具编排。

编排策略主要有三种:并行调用——多个工具之间没有依赖,可以同时执行;顺序依赖——后一个工具的参数依赖前一个工具的结果,必须排队执行;工具路由——模型根据用户意图和工具描述,动态决定调用哪个工具,而不是写死调用顺序。并行执行能大幅节省时间:

幕僚长的价值正在于此:他不只是跑腿,而是知道哪个部门可以同时联系、哪个必须等前一个回执。Agent 的多工具编排能力越强,处理复杂任务的能力就越强;这就像一位经验丰富的幕僚长,能把一摊乱事安排得井井有条。

Ttotal={max⁡itiparallel∑itisequential\displaystyle T_{\text{total}} = \begin{cases} \max_i t_i & \text{parallel} \\ \sum_i t_i & \text{sequential} \end{cases}
如果工具之间无依赖,总耗时是其中最慢的一个;如果有依赖,则要把耗时依次加起来。
  1. 并行情形所有工具一起开工,总时间等于最慢的那个工具的耗时。
    Ttotal=max⁡itiT_{\text{total}} = \max_i t_i
  2. 串行情形一个工具执行完才能执行下一个,总时间是所有耗时之和。
    Ttotal=∑itiT_{\text{total}} = \sum_i t_i
  3. 依赖判断到底用哪种方式,取决于不存在数据依赖(parallel)还是存在依赖(sequential)。
    dependency?\text{dependency?}

开放挑战与标准化

故事还没结束。公司扩张得很快,每天都有新部门成立,服务手册也在不断更新。幕僚长偶尔会遇到完全没见过的部门,只能凭手册上的寥寥数语推断怎么填单。能不能办成事,考验的就是“开放世界泛化”和“鲁棒性”。

对工具调用来说,开放世界泛化指的是:模型在新工具从未出现在训练数据中时,能不能仅凭 name 和 description 推断出正确用法。鲁棒性则指:当参数类型不匹配、工具返回异常、或模型幻觉编造了不存在的工具名时,系统能不能优雅地恢复。目前业界正通过统一接口规范和评估基准(比如 MCP、函数调用基准测试)来推动生态成熟。这个挑战可以形式化地表示为泛化误差:

幕僚长不可能背下所有部门的所有规则,但他读得懂手册背后的逻辑;模型也不可能记住所有工具,但它可以通过语义理解新工具的 Schema。标准化的工作,就是让每一本新手册都采用同一种“语言”,让幕僚长面对新部门时也能从容应对。

ϵgen=E(T,q)[1[y^≠y]]\displaystyle \epsilon_{\text{gen}} = \mathbb{E}_{(\mathcal{T}, q)} [\mathbb{1}[\hat{y} \neq y]]
它衡量的是:面对新工具集合 T 和问题 q,模型预测的工具调用是否正确。
  1. 期望符号E 表示在所有可能的新工具和问题组合上取平均,代表平均表现。
    E(T,q)\mathbb{E}_{(\mathcal{T}, q)}
  2. 预测对比ŷ 是模型猜测的调用,y 是真实正确的调用,两者用不等号比较。
    y^≠y\hat{y} \neq y
  3. 指示函数1[……] 表示如果预测错了就记 1,否则记 0,用来统计错误率。
    1[y^≠y]\mathbb{1}[\hat{y} \neq y]

AI 应用场景

智能客服:查单与退换货

阶段:Agent 工具调用 + 任务执行

解决的问题:传统客服机器人只能根据固定话术回复,遇到“我的快递到哪了”这类问题就答非所问。引入工具调用后,模型可以输出调用“查询物流 API”的请求,宿主程序拿到真实物流信息,再组织成自然语言回复。

具体案例:用户说“订单 12345 为什么还没到”,Agent 调用 get_order_status(order_id="12345"),API 返回“运输中,预计明天到达”,模型据此回复用户。整个流程就像幕僚长替老板打电话问物流公司。

数据助手:自然语言转查询

阶段:工具路由 + 结果解析融合

解决的问题:业务人员不会写 SQL,数据分析师又被重复查询淹没。Agent 可以根据用户问题路由到数据库查询工具,执行 SQL,把返回的结果解析后生成图表或摘要。

具体案例:用户问“本月各区域销售额对比”,模型调用 query_database(sql=...),工具返回 JSON 数据,模型再调用 render_chart(data) 生成图表。这就像幕僚长同时拉通了数据部门和设计部门。

智能运维:故障定位与通知

阶段:多工具编排 + 多轮迭代

解决的问题:运维人员需要登录多台机器查指标、翻日志,效率低。Agent 可以并行调用监控 API 和日志查询工具,拿到线索后再调用脚本执行诊断,最后把结论发给值班群。

具体案例:告警触发后,Agent 先并行调用两个指标查询工具,发现 CPU 飙高,再按依赖顺序调用日志分析工具,定位到慢查询,最后调用消息推送工具通知负责人。就像一个经验老练的幕僚长同时调度多个部门。

Python 演示

python50 行
import json def get_weather(city):    table = {"北京": "晴, 26度", "上海": "多云, 29度"}    return table.get(city, "未知城市") def calculator(expr):    try:        return str(eval(expr))    except Exception as e:        return "计算错误: %s" % e TOOLS = {    "get_weather": {"fn": get_weather, "params": ["city"]},    "calculator": {"fn": calculator, "params": ["expr"]}} def call_tool(name, arguments):    tool = TOOLS.get(name)    if tool is None:        return json.dumps({"error": "unknown tool: %s" % name})    try:        result = tool["fn"](**arguments)    except Exception as e:        result = "执行异常: %s" % e    return json.dumps({"name": name, "result": result}) def llm_reply(user_input):    calls = []    if "天气" in user_input:        calls.append({"name": "get_weather", "arguments": {"city": "北京"}})    if "计算" in user_input:        calls.append({"name": "calculator", "arguments": {"expr": "1+2"}})    if calls:        return {"tool_calls": calls}    return {"content": "需要我帮你查天气或做计算吗?"} def main():    user_input = "北京天气怎么样? 顺便计算 1+2"    reply = llm_reply(user_input)    if "tool_calls" in reply:        results = [call_tool(c["name"], c["arguments"]) for c in reply["tool_calls"]]        final_answer = "工具返回: " + " | ".join(results)    else:        final_answer = reply["content"]    print("用户: %s" % user_input)    print("Assistant: %s" % final_answer) if __name__ == "__main__":    main()
目的:用纯 Python 实现一个最小可运行的工具调用循环,演示模型生成调用请求、宿主执行工具、结果回传的完整流程。
  1. TOOLS = {...}模拟“服务手册”,登记每个工具的函数和可接收参数名。
  2. llm_reply(user_input)模拟模型输出 tool_calls 结构化请求,这里用规则替代真正的大模型。
  3. call_tool(name, arguments)宿主程序根据工具名查找函数,并用参数调用它。
  4. tool["fn"](**arguments)Python 的 ** 语法把字典展开成关键字参数,等价于按工单执行。
  5. json.dumps(...)把工具返回结果序列化成 JSON 字符串,方便回填给模型。
  6. if "tool_calls" in reply:判断模型是否要求调用工具,这是流程的转折点。
  7. results = [call_tool(...) for ...]支持一次请求并行处理多个工具调用,体现多工具编排。
  8. final_answer = "工具返回: " + ...简单演示把多工具结果拼成最终输出。

常见误区

误区我应该让大模型直接执行 Python 代码或者调用 API,只要告诉它“去查一下数据库”就行了。

正解大模型没有任何执行环境,它只会生成文本。工具调用中,模型负责输出“我想调用 function_name,参数是 xxx”的请求,真正执行函数的是你的宿主代码。就像幕僚长写工单,跑腿的是专员,不是幕僚长本人。理解了这一点,你才能正确设计 Agent 架构,把执行权放到宿主侧。

为何常见:因为“AI 帮我做事”的直觉印象太强,很多人忽略了模型和宿主程序之间的分工,导致他们找不到“执行到底发生在哪一层”。

误区反正 Python 是动态类型语言,我传字符串 "123" 和传数字 123 差不多吧,工具应该能自动转换。

正解工具定义的 JSON Schema 明确约定了参数类型、必填项和取值范围。如果模型生成 {"city": 123} 而 Schema 要求 string,轻则校验失败,重则触发类型转换导致业务数据错乱。结构化参数构造是工具调用的“质量红线”。你可以像工单审核员一样,在调用前做一次 schema 校验。

为何常见:日常编程中动态类型很自由,让人低估了异构系统对接时严格类型约束的重要性。

误区工具返回什么,模型就会自动看到什么,所以不需要额外处理结果。

正解工具结果必须由宿主程序读取、解析、序列化,再以文本消息的形式拼回对话上下文,模型才能“看到”。如果你的代码没有把结果传给模型,模型只会继续给出笼统回答。这就像专员把报表放在门口没拿进办公室,幕僚长根本不知道已经有人送过材料。

为何常见:很多人把“返回值”和“上下文”混为一谈,忘了模型能感知的只有它的输入文本。

误区如果任务需要两个工具,我只能让用户分两轮问,或写死先后顺序。

正解现代工具调用协议允许模型在一次回复中输出多个 tool_calls,宿主可以并行执行无依赖的工具,也可以按依赖关系编排执行顺序。多工具编排让 Agent 能像幕僚长一样同时联系多个部门,而不是低效地一件件跑腿。

为何常见:初识工具调用时,大多数教程只展示单个工具的例子,容易让人形成“一轮只能调一次”的思维定式。

面试题

Q1. 在工具调用流程中,真正负责执行外部工具的是谁?

单选题 · easy

  • A.大语言模型自身
  • B.宿主程序或运行时← 正确答案
  • C.用户手工编写代码
  • D.操作系统内核心

答案:B。模型只输出调用请求,不执行任何程序;宿主程序根据 tool_calls 找到对应函数并调用它。就像幕僚长写工单、跑腿专员去执行,角色边界非常清晰。

Q2. 工具定义中通常不包含以下哪一项?

单选题 · easy

  • A.函数名和签名
  • B.自然语言描述
  • C.参数结构 Schema
  • D.模型的训练语料← 正确答案

答案:D。工具定义是给模型看的“服务手册”,包含函数名、描述和参数 Schema;训练语料不是某个工具的属性。没有哪本服务手册会写员工的童年往事,对吧?

Q3. 模型输出 tool_calls 之后,宿主程序第一步应该做什么?

单选题 · easy

  • A.直接把输出展示给用户
  • B.解析工具名和参数并执行← 正确答案
  • C.把该输出加入训练集
  • D.丢弃请求并重新生成

答案:B。tool_calls 是结构化调用请求,宿主程序要解析出工具名和 arguments,然后执行对应函数。好比幕僚长写好工单,下一步就是把工单派出去,而不是把工单裱起来给老板看。

Q4. JSON Schema 中 required 字段的作用是什么?

单选题 · mid

  • A.指定返回值的编码格式
  • B.声明调用时必须提供的参数← 正确答案
  • C.定义函数在系统中的别名
  • D.限制工具的每秒调用次数

答案:B。required 列出必填参数,缺失会让校验失败。就像申请表注明“手机号必填”,漏填就交不了单;这个字段确保模型填的参数足够完整。

Q5. 工具返回的 JSON 解析失败时,合理的做法是什么?

单选题 · mid

  • A.忽略错误并继续正常对话
  • B.捕获异常并把错误回传模型← 正确答案
  • C.随机生成一个替代结果
  • D.立即终止整个 Agent 进程

答案:B。解析失败时,把错误信息回传给模型,让它知道发生了什么、尝试换一种参数或换工具,这才是鲁棒性的体现。幕僚长发现专员没办成事,不会假装办成,而是回去如实汇报并重新协调。

Q6. 多工具编排中,以下哪些策略是合理的?

多选题 · mid

  • A.无依赖工具并行调用← 正确答案
  • B.有依赖工具顺序执行← 正确答案
  • C.根据意图动态路由选择← 正确答案
  • D.永远只调用第一个工具

答案:ABC。并行、顺序、动态路由都是常见编排策略;“永远只调用第一个工具”忽略了用户实际意图,是错误的。幕僚长会按需调部门,而不是只找电话本上第一个号码。

Q7. 关于工具调用的作用,下列说法正确的是哪一项?

单选题 · mid

  • A.它让模型具备读写外部世界的能力← 正确答案
  • B.它让模型拥有无限大的上下文
  • C.它让模型不再需要预训练数据
  • D.它让模型完全不会产生幻觉

答案:A。工具调用让模型通过宿主程序访问外部 API 和数据,是“借来的手”;它不能解决上下文容量、预训练依赖或幻觉问题。幕僚长虽然能跑腿办事,但不可能因此变成全知全能。

Q8. 工具调用在开放世界中的泛化能力,主要指什么?

单选题 · hard

  • A.模型能根据新工具的描述推断用法← 正确答案
  • B.模型生成工具调用速度足够快
  • C.模型能记住所有历史工具名称
  • D.模型能自动修改工具源码

答案:A。开放世界泛化是指面对训练时没见过的工具,模型仅凭 name 和 description 就能正确构造参数。如同幕僚长第一次见到新部门,也能根据手册推敲出怎么填单;背下所有工具名不是泛化。

Q9. 关于工具调用的标准化,下列哪些方向正在推进?

多选题 · hard

  • A.统一接口规范← 正确答案
  • B.统一评估基准← 正确答案
  • C.共享参数 Schema 标准← 正确答案
  • D.禁止第三方工具接入

答案:ABC。标准化包括接口规范、评估基准、Schema 标准等;禁止第三方工具接入恰恰与开放生态背道而驰。就像全行业统一工单格式,而不是让大家别用新部门。

Q10. 以下哪些情形会显著降低工具调用的鲁棒性?

多选题 · extreme

  • A.参数类型与 Schema 不匹配← 正确答案
  • B.工具返回空值或异常结构← 正确答案
  • C.模型虚构不存在的工具名← 正确答案
  • D.用户提问的句子长度较长

答案:ABC。参数类型错误、异常返回、幻觉工具名都会破坏调用链;用户问题较长并不必然降低鲁棒性。幕僚长最怕的是工单格式错、部门不存在、对方乱回复,而不是老板多问了几句。