在大模型从单纯的对话交互走向实际落地的过程中,Function Call作为连接语言模型与外部能力的关键机制,已经成为行业标配。但很多人对Skill和Function Call的关系仍然存在困惑。作为一名长期从事大模型应用开发的工程师,我将从实战角度详细解析二者的区别与联系。
大模型原生存在三个显著短板:
以计算任务为例,让GPT-4直接计算"12345×6789"时,错误率高达37%(来自OpenAI官方测试数据)。而通过Function Call调用计算器函数,准确率可达100%。
Skill(技能):是大模型能力的标准化封装。就像给智能手机安装APP:
Function Call(函数调用):是使用这些APP的操作流程。包含以下步骤:
关键区别:Skill定义"能做什么",Function Call实现"怎么做"
一个规范的Skill定义包含以下要素(以JSON Schema为例):
json复制{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"skill_name": {
"type": "string",
"description": "技能唯一标识符"
},
"description": {
"type": "string",
"description": "技能功能描述"
},
"parameters": {
"type": "object",
"properties": {
"param1": {
"type": "string",
"enum": ["value1", "value2"],
"description": "参数说明"
}
},
"required": ["param1"]
},
"return_type": {
"type": "string",
"enum": ["string", "number", "boolean", "object"]
}
},
"required": ["skill_name", "description"]
}
原子性设计:每个Skill应聚焦单一功能。例如:
参数校验:通过JSON Schema严格定义参数:
json复制"parameters": {
"base_currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"target_currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
}
}
错误处理:预定义异常类型:
python复制class SkillError(Exception):
ERROR_CODES = {
400: "Invalid parameters",
503: "Service unavailable"
}
适用于计算密集型任务,典型实现:
python复制def local_calculator(params: dict) -> float:
"""
本地计算器实现
参数示例: {"num1": 10, "num2": 20, "op": "add"}
"""
ops = {
"add": lambda x,y: x+y,
"sub": lambda x,y: x-y,
"mul": lambda x,y: x*y,
"div": lambda x,y: x/y if y!=0 else float('nan')
}
return ops[params["op"]](params["num1"], params["num2"])
性能优化技巧:
适用于数据获取类任务,推荐实现:
python复制async def weather_query(params: dict) -> dict:
"""
天气查询Skill
参数示例: {"location": "北京", "date": "2024-03-20"}
"""
async with aiohttp.ClientSession() as session:
async with session.get(
"https://api.weather.com/v3/wx/forecast",
params={
"location": params["location"],
"date": params["date"],
"apiKey": os.getenv("WEATHER_API_KEY")
},
timeout=3
) as resp:
if resp.status == 200:
return await resp.json()
raise SkillError(503, "服务不可用")
可靠性保障措施:
复杂任务的实现范式:
python复制def travel_planner(params: dict) -> dict:
"""
出行规划组合Skill
参数示例: {"origin": "北京", "destination": "上海", "date": "2024-03-20"}
"""
# 并行调用子Skill
with ThreadPoolExecutor() as executor:
weather_future = executor.submit(
weather_query, {"location": params["destination"]}
)
flight_future = executor.submit(
flight_search, {"from": params["origin"], "to": params["destination"]}
)
weather = weather_future.result()
flights = flight_future.result()
# 结果整合
return {
"weather": weather,
"flights": flights,
"suggestion": generate_suggestion(weather, flights)
}
设计原则:
以"查询北京明天天气"为例的调用时序:
mermaid复制sequenceDiagram
participant User
participant LLM
participant SkillManager
participant WeatherAPI
User->>LLM: "北京明天天气怎么样?"
LLM->>SkillManager: 获取可用Skill列表
SkillManager-->>LLM: ["weather", "calculator", ...]
LLM->>LLM: 意图识别→选择weather Skill
LLM->>LLM: 参数提取→location="北京", date=明天
LLM->>SkillManager: 生成调用指令
SkillManager->>WeatherAPI: 调用天气查询API
WeatherAPI-->>SkillManager: 返回天气数据
SkillManager-->>LLM: 结构化结果
LLM->>User: "北京明天晴转多云,15-22℃"
使用few-shot prompt提升准确率:
python复制prompt = """
你是一个智能助手,可以调用以下工具:
1. weather - 查询天气,参数: location(str), date(str)
2. calculator - 数学计算,参数: num1(float), num2(float), op(str)
3. translator - 文本翻译,参数: text(str), target_lang(str)
请根据用户问题判断是否需要调用工具,示例:
用户:今天上海气温
→ 需要调用weather工具,参数: {"location": "上海", "date": "今天"}
用户:3.14乘以2
→ 需要调用calculator工具,参数: {"num1": 3.14, "num2": 2, "op": "mul"}
当前用户问题:{}
"""
结合实体识别和语义解析:
python复制def extract_parameters(query: str, skill_schema: dict) -> dict:
# 使用spaCy进行实体识别
nlp = spacy.load("zh_core_web_sm")
doc = nlp(query)
params = {}
for ent in doc.ents:
if ent.label_ == "LOC":
params["location"] = ent.text
elif ent.label_ == "DATE":
params["date"] = parse_date(ent.text)
# 补充默认值
for param in skill_schema["required"]:
if param not in params:
params[param] = skill_schema["parameters"][param]["default"]
return params
基于Python的动态调用:
python复制class SkillEngine:
def __init__(self):
self.skills = {}
def register(self, skill_name: str, skill_func: callable):
self.skills[skill_name] = skill_func
async def execute(self, call: dict) -> dict:
try:
result = await self.skills[call["name"]](call["parameters"])
return {"status": "success", "data": result}
except Exception as e:
return {"status": "error", "message": str(e)}
python复制class SkillManager:
def __init__(self):
self.skill_registry = {}
self.skill_metadata = {}
def add_skill(self, skill: dict):
# 校验Skill定义
validate_schema(skill["schema"])
# 注册元数据
self.skill_metadata[skill["name"]] = {
"description": skill["description"],
"parameters": skill["parameters"]
}
# 注册执行函数
self.skill_registry[skill["name"]] = skill["function"]
def get_skill_list(self) -> list:
return [
{
"name": name,
"description": meta["description"],
"parameters": meta["parameters"]
}
for name, meta in self.skill_metadata.items()
]
async def invoke(self, skill_name: str, params: dict) -> any:
# 参数校验
schema = self.skill_metadata[skill_name]["parameters"]
validate_params(params, schema)
# 执行调用
return await self.skill_registry[skill_name](params)
python复制async def batch_invoke(tasks: list) -> dict:
# 按Skill类型分组
task_groups = defaultdict(list)
for task in tasks:
task_groups[task["skill"]].append(task)
# 并行处理同类型调用
results = {}
async with asyncio.TaskGroup() as tg:
for skill, group in task_groups.items():
if skill == "weather":
results.update(await weather_batch(group))
elif skill == "calculator":
results.update(await calculator_batch(group))
return results
python复制from functools import lru_cache
@lru_cache(maxsize=1024)
def cached_calculator(num1: float, num2: float, op: str) -> float:
return basic_calculator(num1, num2, op)
python复制def check_permission(user: User, skill: str) -> bool:
permissions = {
"basic": ["calculator", "translator"],
"premium": ["weather", "stock"],
"admin": ["db_query", "system_ctl"]
}
return skill in permissions[user.role]
python复制def sanitize_input(input_str: str) -> str:
# 移除特殊字符
cleaned = re.sub(r"[^\w\s\u4e00-\u9fff]", "", input_str)
# 截断超长输入
return cleaned[:1000]
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 模型未触发Function Call | 1. Prompt未明确说明 2. 模型版本不支持 |
1. 在system prompt添加调用规则 2. 使用hunyuan-functioncall专用模型 |
| 参数提取错误 | 1. 实体识别失败 2. 同义词未映射 |
1. 增强NER模型 2. 添加同义词词典 |
| 执行超时 | 1. API响应慢 2. 网络延迟 |
1. 设置合理超时(3-5s) 2. 添加重试机制 |
| 权限拒绝 | 1. 未授权访问 2. 角色权限不足 |
1. 检查token有效性 2. 调整用户角色 |
python复制logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s',
level=logging.INFO,
handlers=[
logging.FileHandler('skill_debug.log'),
logging.StreamHandler()
]
)
python复制def test_skill(skill_name: str):
print(f"Testing {skill_name}...")
while True:
query = input("输入测试语句(输入q退出): ")
if query == "q":
break
# 模拟LLM处理流程
intent = recognize_intent(query)
if intent != skill_name:
print("意图识别错误")
continue
params = extract_parameters(query, get_schema(skill_name))
result = execute_skill(skill_name, params)
print("结果:", result)
python复制async def load_remote_skill(url: str):
resp = await http.get(url)
skill = resp.json()
manager.register(skill)
python复制def auto_orchestrate(query: str) -> list:
plan = llm.generate(f"""
根据用户问题规划Skill执行流程:
问题:{query}
可用Skill:{get_skill_list()}
输出JSON格式的执行计划
""")
return json.loads(plan)
code复制┌────────────────┐
│ API Gateway │
└───────┬────────┘
│
┌───────▼────────┐
│ Skill Manager │
└───────┬────────┘
│
┌───────▼────────┐
│ Skill Execution │
└───────┬────────┘
│
┌───────▼────────┐
│ External Systems│
└────────────────┘
python复制metrics = {
"invocation_count": Counter("skill_invocations"),
"latency": Histogram("skill_latency_seconds", buckets=[0.1, 0.5, 1]),
"error_rate": Gauge("skill_error_percentage")
}
def track_metrics(func):
@wraps(func)
async def wrapper(*args, **kwargs):
start = time.time()
metrics["invocation_count"].inc()
try:
result = await func(*args, **kwargs)
metrics["latency"].observe(time.time() - start)
return result
except Exception:
metrics["error_rate"].inc()
raise
return wrapper
在实际项目开发中,建议采用渐进式策略:先从简单的计算类Skill入手,逐步扩展到API集成,最后实现复杂组合Skill。同时要建立完善的测试体系,包括单元测试(验证单个Skill)、集成测试(验证Skill组合)和端到端测试(验证完整业务流程)。