5 步让大模型串联 10+ 内部 API:Function Calling 进阶实战

6 阅读6分钟

用 Function Calling 把企业内部 API 串联成自动化工作流,附完整 Python 代码。


为什么需要 Function Calling 进阶?

基础版 Function Calling 只能调一个 API,但实际业务场景是:

  • 查客户信息 → 调用 CRM API
  • 查库存 → 调用 ERP API
  • 创建工单 → 调用 OA API
  • 发通知 → 调用企微 API

一个用户问题,需要串联多个内部系统。 这就是本文要解决的问题。


核心架构设计

用户提问
   ↓
大模型分析意图
   ↓
决定调用哪些 API(多步骤规划)
   ↓
依次执行 API 调用
   ↓
汇总结果,生成回复

Step 1:定义内部 API 工具集

假设我们有 3 个内部系统:CRM、库存管理、通知系统。

# internal_apis.py
import requests
import json

# 配置
API_BASE = "https://internal-api.company.com"
HEADERS = {"Authorization": "Bearer YOUR_TOKEN"}

def crm_get_customer(phone: str) -> dict:
    """根据手机号查询客户信息"""
    resp = requests.get(
        f"{API_BASE}/crm/customer",
        params={"phone": phone},
        headers=HEADERS,
        timeout=10
    )
    resp.raise_for_status()
    return resp.json()

def crm_create_order(customer_id: str, product: str, amount: float) -> dict:
    """为客户创建订单"""
    resp = requests.post(
        f"{API_BASE}/crm/order",
        json={"customer_id": customer_id, "product": product, "amount": amount},
        headers=HEADERS,
        timeout=10
    )
    resp.raise_for_status()
    return resp.json()

def inventory_check(product_id: str) -> dict:
    """查询库存数量"""
    resp = requests.get(
        f"{API_BASE}/inventory/check",
        params={"product_id": product_id},
        headers=HEADERS,
        timeout=10
    )
    resp.raise_for_status()
    return resp.json()

def notify_wechat(user_id: str, message: str) -> dict:
    """发送企微通知"""
    resp = requests.post(
        f"{API_BASE}/notify/wechat",
        json={"user_id": user_id, "message": message},
        headers=HEADERS,
        timeout=10
    )
    resp.raise_for_status()
    return resp.json()

# 把所有 API 封装成可调用的方法字典
AVAILABLE_FUNCTIONS = {
    "crm_get_customer": crm_get_customer,
    "crm_create_order": crm_create_order,
    "inventory_check": inventory_check,
    "notify_wechat": notify_wechat,
}

Step 2:定义 Function 描述(给大模型看)

# function_definitions.py
functions = [
    {
        "name": "crm_get_customer",
        "description": "根据手机号查询客户信息,返回客户ID、姓名、会员等级",
        "parameters": {
            "type": "object",
            "properties": {
                "phone": {
                    "type": "string",
                    "description": "客户手机号,如 13800138000"
                }
            },
            "required": ["phone"]
        }
    },
    {
        "name": "crm_create_order",
        "description": "为客户创建订单,需要客户ID、产品名称和金额",
        "parameters": {
            "type": "object",
            "properties": {
                "customer_id": {"type": "string", "description": "客户ID"},
                "product": {"type": "string", "description": "产品名称"},
                "amount": {"type": "number", "description": "订单金额"}
            },
            "required": ["customer_id", "product", "amount"]
        }
    },
    {
        "name": "inventory_check",
        "description": "查询指定产品的库存数量",
        "parameters": {
            "type": "object",
            "properties": {
                "product_id": {"type": "string", "description": "产品ID"}
            },
            "required": ["product_id"]
        }
    },
    {
        "name": "notify_wechat",
        "description": "向指定用户发送企微通知",
        "parameters": {
            "type": "object",
            "properties": {
                "user_id": {"type": "string", "description": "企微用户ID"},
                "message": {"type": "string", "description": "通知内容"}
            },
            "required": ["user_id", "message"]
        }
    }
]

Step 3:实现多步 Function Calling 循环

# agent.py
import openai
import json
from internal_apis import AVAILABLE_FUNCTIONS
from function_definitions import functions

client = openai.OpenAI(api_key="YOUR_API_KEY")

def run_agent(user_query: str) -> str:
    """
    多步 Function Calling Agent
    支持连续调用多个 API,直到大模型认为可以回复用户
    """
    messages = [
        {
            "role": "system",
            "content": "你是企业智能助手,可以调用内部API帮用户查询客户信息、创建订单、查库存、发通知。如果缺少参数,主动向用户询问。"
        },
        {"role": "user", "content": user_query}
    ]
    
    max_iterations = 10  # 防止无限循环
    iteration = 0
    
    while iteration < max_iterations:
        iteration += 1
        
        # 调用大模型,启用 Function Calling
        response = client.chat.completions.create(
            model="gpt-4o",
            messages=messages,
            tools=[{"type": "function", "function": f} for f in functions],
            tool_choice="auto"  # 让模型自己决定是否调用函数
        )
        
        message = response.choices[0].message
        
        # 情况1:大模型直接回复(不需要调用函数)
        if not message.tool_calls:
            return message.content
        
        # 情况2:大模型要求调用函数
        messages.append(message)  # 把模型的「我想调用函数」加入历史
        
        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            function_args = json.loads(tool_call.function.arguments)
            
            print(f"🔧 调用函数: {function_name}, 参数: {function_args}")
            
            # 执行对应的内部 API
            if function_name in AVAILABLE_FUNCTIONS:
                func = AVAILABLE_FUNCTIONS[function_name]
                try:
                    result = func(**function_args)
                    result_str = json.dumps(result, ensure_ascii=False)
                except Exception as e:
                    result_str = json.dumps({"error": str(e)}, ensure_ascii=False)
            else:
                result_str = json.dumps({"error": "函数不存在"}, ensure_ascii=False)
            
            # 把函数执行结果返回给大模型
            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "name": function_name,
                "content": result_str
            })
    
    return "执行步骤过多,已终止"

# 测试
if __name__ == "__main__":
    result = run_agent("帮我用手机 13800138000 查一下这个客户的信息,然后通知他订单已发货")
    print("🤖 回复:", result)

Step 4:增加上下文记忆和错误处理

# agent_v2.py - 增强版
import sqlite3
from datetime import datetime

class FunctionCallAgent:
    def __init__(self, model="gpt-4o"):
        self.client = openai.OpenAI(api_key="YOUR_API_KEY")
        self.model = model
        self.db = sqlite3.connect("agent_history.db", check_same_thread=False)
        self.init_db()
    
    def init_db(self):
        """初始化历史记录数据库"""
        self.db.execute("""
        CREATE TABLE IF NOT EXISTS call_history (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            timestamp TEXT,
            user_query TEXT,
            function_name TEXT,
            function_args TEXT,
            result TEXT
        )
        """)
        self.db.commit()
    
    def log_call(self, query, func_name, args, result):
        """记录每次函数调用"""
        self.db.execute(
            "INSERT INTO call_history (timestamp, user_query, function_name, function_args, result) VALUES (?, ?, ?, ?, ?)",
            (datetime.now().isoformat(), query, func_name, json.dumps(args), str(result))
        )
        self.db.commit()
    
    def execute_function_with_retry(self, func_name, args, max_retry=3):
        """带重试的函数执行"""
        func = AVAILABLE_FUNCTIONS.get(func_name)
        if not func:
            return {"error": f"函数 {func_name} 不存在"}
        
        for attempt in range(max_retry):
            try:
                result = func(**args)
                return result
            except requests.exceptions.Timeout:
                if attempt == max_retry - 1:
                    return {"error": "API 超时,请稍后重试"}
                time.sleep(2 ** attempt)  # 指数退避
            except Exception as e:
                return {"error": str(e)}
    
    def run(self, user_query: str) -> str:
        messages = [
            {"role": "system", "content": "你是企业智能助手..."},
            {"role": "user", "content": user_query}
        ]
        
        while True:
            response = self.client.chat.completions.create(
                model=self.model,
                messages=messages,
                tools=[{"type": "function", "function": f} for f in functions]
            )
            
            message = response.choices[0].message
            
            if not message.tool_calls:
                return message.content
            
            messages.append(message)
            
            for tool_call in message.tool_calls:
                func_name = tool_call.function.name
                func_args = json.loads(tool_call.function.arguments)
                
                result = self.execute_function_with_retry(func_name, func_args)
                self.log_call(user_query, func_name, func_args, result)
                
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "name": func_name,
                    "content": json.dumps(result, ensure_ascii=False)
                })

Step 5:实战演示

# main.py
from agent_v2 import FunctionCallAgent

agent = FunctionCallAgent()

# 场景1:查询客户 + 通知
response = agent.run("手机号 13800138000 的客户是谁?查到后发企微通知他会员已升级")
print(response)

# 场景2:查库存 + 创建订单
response = agent.run("查一下产品 PROD-001 的库存,如果大于10就给客户 CUST-99 创建订单")
print(response)

执行流程(场景2):

用户: 查一下产品 PROD-001 的库存,如果大于10就给客户 CUST-99 创建订单

🔧 第1步: inventory_check(product_id="PROD-001")
   ← 返回: {"stock": 25, "product_name": "腾讯云服务器3折券"}

🤖 大模型思考: 库存25 > 10,需要创建订单,但还缺客户信息...
🔧 第2步: crm_get_customer(phone=?) 
   ← 大模型发现缺参数,询问用户客户手机号

用户: 13800138000

🔧 第3步: crm_get_customer(phone="13800138000")
   ← 返回: {"customer_id": "CUST-99", "name": "张三", "level": "VIP"}

🔧 第4步: crm_create_order(customer_id="CUST-99", product="腾讯云服务器3折券", amount=99.0)
   ← 返回: {"order_id": "ORD-20240505-001", "status": "success"}

🤖 最终回复: 已为张三(VIP客户)创建订单 ORD-20240505-001,产品:腾讯云服务器3折券,金额:99元。

进阶技巧

1. 并行调用多个 API(提速)

# 使用 ThreadPoolExecutor 并行调用
from concurrent.futures import ThreadPoolExecutor

def parallel_function_calls(tool_calls):
    with ThreadPoolExecutor() as executor:
        futures = []
        for tc in tool_calls:
            args = json.loads(tc.function.arguments)
            futures.append(executor.submit(AVAILABLE_FUNCTIONS[tc.function.name], **args))
        return [f.result() for f in futures]

2. 函数调用结果缓存

import hashlib
import pickle
from pathlib import Path

cache_dir = Path("./function_cache")
cache_dir.mkdir(exist_ok=True)

def cached_call(func_name, args):
    key = hashlib.md5(f"{func_name}:{json.dumps(args, sort_keys=True)}".encode()).hexdigest()
    cache_file = cache_dir / key
    
    if cache_file.exists():
        return pickle.loads(cache_file.read_bytes())
    
    result = AVAILABLE_FUNCTIONS[func_name](**args)
    cache_file.write_bytes(pickle.dumps(result))
    return result

总结

功能基础版进阶版(本文)
单 API 调用
多 API 串联
错误重试
调用历史记录
并行调用
结果缓存

核心要点:

  1. 用循环实现多步 Function Calling
  2. 每次把函数执行结果追加到 messages
  3. 加强错误处理和重试机制

👤 作者简介

一枚在大中原腹地(河南)卖公有云的从业者,主营腾讯云/阿里云/火山云,曾踩坑无数,现专注AI大模型应用落地。关注公众号「公有云cloud」,围观AI前沿动态~

博客:yunduancloud.icu