你写了个函数,但什么时候调用它——你说了不算
本文最后更新于 2026-05-10,文章内容可能已经过时。
你写了个函数,但什么时候调用它——你说了不算
你有没有遇到过这种情况:写了一个函数,逻辑完全正确,测试也通过了,但它什么时候被调用,完全不是你决定的?
第一次遇到这个感觉,很多人会觉得有点失控。但当你理解了背后的设计逻辑,你会发现——这其实是 Agent 开发里最有意思的地方。
这篇文章的目标很具体:带你用 Perplexity 官方的 Agent Skills 手册,从零跑通一个最简单的技能模块。不追求完整产品,只追求流程全跑通。跑完之后,你会遇到一个让人"哦,原来如此"的认知翻转时刻——我先卖个关子,等你跑到第四章自然就明白了。
---
第一章:为什么选 Perplexity Agent Skills 手册来入门?
市面上讲 Agent 的文章不少,但大多数要么太理论(讲 ReAct、CoT、Tool Use 框架,说完你还是不知道从哪里写第一行代码),要么太产品化(直接教你用某个 no-code 平台拖拖拽拽,根本不接触底层)。
Perplexity 的 Agent Skills 文档走的是第三条路:工程师视角的接线说明书。
它假设你已经有一点 API 调用基础,但从没做过 Agent,然后用最直接的方式告诉你:一个 Skill 长什么样、怎么注册、Agent 怎么识别并调用它。没有多余的哲学,全是接线图。
这正是我推荐它作为 Agent 入门材料的原因。它不会让你在"Agent 到底是什么"这个问题上绕圈子,而是直接让你动手,然后在动手过程中自然理解那些概念。
本文只搭最简单的一个技能模块——一个"查今日天气"的 Skill。不追求完整产品,只追求流程全跑通。
跑完之后你会发现,有一个环节的设计逻辑和你写普通函数的直觉完全相反。理解这个"反转",才算真正入门 Agent 开发。
---
第二章:动手前先读懂手册的三个核心概念
在打开编辑器之前,有三个概念必须先搞清楚。不然代码写出来了,你也不知道哪里出了问题。
概念一:Skill 定义结构
Skill 不是一个普通的函数定义,它是一份给 AI 看的说明书。
类比:想象你在一家餐厅,菜单上写着"红烧肉——选用五花肉,口感软糯,适合喜欢浓郁口味的食客"。这段描述不是给你的,是给服务员看的,让服务员知道什么时候该向什么样的客人推荐这道菜。
Skill 定义结构就是这份菜单描述。它用 JSON Schema 的格式,告诉 Agent:这个技能叫什么名字、能做什么事、需要什么输入、会返回什么输出。
概念二:输入/输出 Schema
Schema 是对数据结构的精确描述。输入 Schema 告诉 Agent"调用这个技能需要提供哪些参数",输出 Schema 告诉 Agent"这个技能会返回什么格式的数据"。
类比:输入 Schema 就像点菜单上的"必填项"——你必须告诉服务员几人份、辣不辣;输出 Schema 就像菜品的摆盘规范——上桌的时候必须是这个样子。
Schema 写得好不好,直接决定 Agent 能不能准确理解并调用你的技能。 这一点在第四章会重点展开。概念三:Skill 的触发机制
这是最容易让新手困惑的地方。
普通函数的触发机制很直接:你在代码里写 get_weather("北京"),它就被调用了。但 Skill 的触发机制完全不同:你不调用它,Agent 读完你的 Skill 描述之后,自己决定要不要调用、什么时候调用。
整个调用链路大致是这样的:
用户发送自然语言请求
↓
Agent 读取所有已注册的 Skill 描述
↓
模型推理:这个请求需要用到哪些 Skill?
↓
决策:调用 get_weather Skill,参数 city="北京"
↓
执行后端函数,获取返回值
↓
Agent 整合结果,生成最终回复
注意第三步——那个推理和决策过程,完全在模型内部发生,你看不到,也控制不了。你能控制的,只有你的 Skill 描述写得够不够清楚。
好,概念铺垫完毕。开始动手。
---
第三章:手把手搭建:一个"查今日天气"最简技能模块
我们要实现的效果:用户发一条"北京今天天气怎么样",Agent 能识别出需要调用天气查询技能,执行查询,然后返回结果。
步骤一:写 Skill 的 JSON Schema 描述
这是整个流程的核心文件。Agent 靠这份描述来理解你的技能。
{
"name": "get_current_weather",
"description": "获取指定城市的当前天气信息,包括温度、天气状况和湿度。当用户询问某个城市的天气、气温、是否需要带伞等问题时,调用此技能。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "需要查询天气的城市名称,例如:北京、上海、广州"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度单位,默认使用 celsius(摄氏度)"
}
},
"required": ["city"]
}
}
注意 description 字段——这不是给你自己看的注释,是给模型看的行为指南。写得越具体,模型越知道什么时候该调用这个技能。
步骤二:实现后端函数逻辑
import json
import requests
def get_current_weather(city: str, unit: str = "celsius") -> dict:
"""
实际项目中这里会调用真实天气 API(如和风天气、OpenWeatherMap)
这里用 mock 数据演示流程
"""
# mock 数据,实际替换为真实 API 调用
mock_weather = {
"北京": {"temp": 22, "condition": "晴", "humidity": 45},
"上海": {"temp": 28, "condition": "多云", "humidity": 72},
"广州": {"temp": 33, "condition": "阵雨", "humidity": 88},
}
weather = mock_weather.get(city, {"temp": 20, "condition": "未知", "humidity": 60})
temp = weather["temp"]
if unit == "fahrenheit":
temp = round(temp * 9/5 + 32, 1)
unit_symbol = "°F"
else:
unit_symbol = "°C"
return {
"city": city,
"temperature": f"{temp}{unit_symbol}",
"condition": weather["condition"],
"humidity": f"{weather['humidity']}%"
}
函数本身非常干净——只负责执行,不判断"该不该执行"。这一点很重要,第四章会详细说。
步骤三:注册到 Agent 调用链
以 OpenAI 兼容格式为例(Perplexity API 同样支持这套接口规范):
from openai import OpenAI
import json
如果你想低成本复现这个 demo,可以把 base_url 换成 api.884819.xyz
它完全兼容 OpenAI 格式,不需要改任何其他代码逻辑
client = OpenAI(
api_key="your_api_key",
base_url="https://api.884819.xyz/v1"
)
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "获取指定城市的当前天气信息...", # 同上面的 Schema
"parameters": { ... } # 同上面的 Schema
}
}
]
发送用户请求
response = client.chat.completions.create(
model="gpt-5.1",
messages=[{"role": "user", "content": "北京今天天气怎么样?"}],
tools=tools,
tool_choice="auto"
)
步骤四:处理模型返回,执行函数
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
# 执行对应函数
if function_name == "get_current_weather":
result = get_current_weather(**function_args)
print(f"Agent 决定调用: {function_name}")
print(f"传入参数: {function_args}")
print(f"函数返回: {result}")
预期终端输出:
Agent 决定调用: get_current_weather
传入参数: {'city': '北京', 'unit': 'celsius'}
函数返回: {'city': '北京', 'temperature': '22°C', 'condition': '晴', 'humidity': '45%'}
流程跑通了。注意看——你没有在任何地方写 if "天气" in user_input: get_current_weather()。你只是把 Skill 描述丢给了模型,模型自己决定要调用它。
这就是那个"反直觉"的地方。
---
第四章:那个"反直觉"的环节——控制权的转移
让我们正式拆解这个认知翻转。
普通函数调用的逻辑:我知道什么时候该调、我主动调用、我决定传什么参数。 Agent Skill 的逻辑:我只负责描述这个函数能做什么,调不调、什么时候调、传什么参数——模型决定。 | 维度 | 普通函数调用 | Agent Skill 调用 | | 谁决定调用时机 | 开发者(硬编码逻辑) | 模型(基于语义理解) | | 参数从哪里来 | 开发者显式传入 | 模型从用户输入中提取 | | 调试方式 | 打断点、看调用栈 | 优化 Skill 描述、看模型推理 | | 函数内部是否需要判断"该不该执行" | 有时需要 | 完全不需要 |这个"控制权转移"带来三个具体影响:
影响一:Schema 描述的质量决定技能被调用的准确率你的函数逻辑写得多漂亮都没用,如果 description 字段写得模糊,模型就是不会调用它。这不是 bug,这是设计。模型的决策完全依赖你给的文字描述。
因为"该不该调用"的判断已经外包给了模型,你的函数只需要做一件事:给定合法输入,返回正确输出。不需要在函数里判断用户意图,不需要处理"这次调用是否合理"的问题。函数变得更纯粹、更好测试。
影响三:调试思路要变当 Skill 没有被正确调用时,你的第一反应不应该是"函数哪里写错了",而应该是"描述写得够不够清楚"。这是一种完全不同的调试直觉。
从"指挥者"变成"规则制定者"——你从写命令式代码(我来告诉程序每一步做什么),切换到写声明式代码(我来描述这个能力是什么,让系统自己决定怎么用)。
这个感觉,和你第一次理解 CSS 的 flexbox、或者第一次用 SQL 替代手写循环时的感觉,非常像。
---
第五章:踩坑总结 + 给不同层级读者的建议
坑一:Skill 描述太模糊,Agent 从不调用
错误示例:{
"name": "get_weather",
"description": "天气查询功能"
}
实际输出: 模型直接用自己的知识回答天气问题,完全没有调用 Skill。
原因分析: "天气查询功能"这五个字对模型来说信息量为零。模型不知道什么场景下该用它,于是选择不用。
修复方式: 在 description 里明确写出触发条件——"当用户询问某城市的天气、气温、是否需要带伞、今天适合出行吗等问题时,调用此技能。"
---
坑二:返回格式不规范,Agent 输出乱码
错误示例: 后端函数返回了一个嵌套很深的对象,或者直接返回了一个字符串而不是结构化数据。 实际输出: Agent 的最终回复里出现原始 JSON 字符串,或者把返回值当成文本直接输出,格式很丑。 原因分析: 模型在整合 Skill 返回值时,需要能"读懂"这个返回值。如果返回的是无结构的字符串,模型不知道哪个字段是温度、哪个是天气状况,只能原样输出。 修复方式: 返回值始终用结构化的 dict,字段命名要语义清晰(temperature 比 t 好,weather_condition 比 wc 好)。
---
调试 Skill 描述准确率的时候需要反复调用模型,API 成本会累积得很快。建议用 [api.884819.xyz](https://api.884819.xyz) 的按量计费模式做调试,跑通后再切换到生产环境。新用户注册即送体验 token,国产模型(Deepseek/千问等)完全免费,没有月租。
---
给不同层级读者的下一步路径
如果你是小白: 先把这个 demo 完整跑一遍,不要跳过任何步骤。遇到报错先看错误信息,80% 的问题是 API key 没配对或者 JSON 格式写错了。 如果你是进阶用户: 尝试再注册一个get_weather_forecast(未来几天预报)技能,然后观察当用户问"北京这周末天气怎么样"时,模型会调用哪一个。这个实验会让你对 Skill 描述的精度有更深的体感。
如果你是高阶用户: 研究 Skill 的并发调用机制——当一个请求需要同时查询两个城市的天气时,模型会串行调用还是并行调用?错误回退机制怎么设计?这些问题在生产环境里都是真实挑战。
---
这篇我们只跑了单个 Skill。但真实场景里,Agent 往往需要同时注册 5-10 个技能,然后自己决定"先调哪个、再调哪个、某个失败了怎么办"。
下一篇我想试一个更有意思的问题:当你给 Agent 注册了两个功能相近的技能,它会怎么选?会不会选错?能不能通过描述来"引导"它的选择?
如果你对这个问题也好奇,点个关注,下周见。
---
本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。#AI教程 #Agent开发 #Perplexity #函数调用 #ToolUse #8848AI #人工智能 #AI实战