今天学什么?

Day 12 你只有一个工具(calculate)。今天加到三个:

工具 大模型什么时候调 用到的之前知识
calculate 用户问数学题 Day 1 eval
get_weather 用户问天气 Day 6 API 调用
get_current_time 用户问时间 Day 8 time 模块

最终效果:用户问什么,大模型自动选最合适的工具。

用户:"123×456等于多少?"   → 大模型选 calculate
用户:"北京天气怎么样?"     → 大模型选 get_weather
用户:"现在几点了?"        → 大模型选 get_current_time
用户:"你好"              → 大模型不用工具,直接聊天

和 Day 12 的区别

Day 12(一个工具) Day 13(三个工具 + 多轮对话)
只有 calculate calculate + get_weather + get_current_time
问一次就结束 while 循环,可以一直问
if 判断一个函数 if-elif 判断多个函数

tools JSON 怎么写三个工具?

tools 是一个列表,每个元素是一个工具(一个字典):

tools = [
    {   # 工具 1:calculate
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "计算数学公式,如 123*456 或 100+200",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "要计算的数学公式"
                    }
                },
                "required": ["expression"]
            }
        }
    },
    {   # 工具 2:get_current_time(无参数)
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "获取当前时间,如现在几点了",
            "parameters": {
                "type": "object",
                "properties": {}   # 没有参数,空对象
            }
        }
    },
    {   # 工具 3:get_weather
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询城市天气,如北京今天天气怎么样",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如北京、上海"
                    }
                },
                "required": ["city"]
            }
        }
    }
]

记忆口诀:tools 是工具箱,每个 {} 是一把工具。加工具就是往工具箱里多放一把。

get_current_time 为什么没有参数?

因为 def get_current_time() -> str: 函数本身不需要参数,所以 properties 是空的 {}

get_weather 的参数名为什么是 city 不是 expression?

因为函数签名是 def get_weather(city: str),参数叫 citytools 里的参数名必须和函数签名里的参数名一致。

三步之间的字段对应关系

tools 里你定义了什么 大模型返回了什么 你的代码读取什么
name: "get_weather" tool_call.function.name = "get_weather" function_name
properties.city tool_call.function.arguments = '{"city":"北京"}' arguments["city"]

完整代码

"""
Day 13:多工具 AI 个人助理
三个工具:计算、天气、时间
输入 q 退出
"""
import time, os
from dotenv import load_dotenv
import httpx
from openai import OpenAI


# -- 第 1 步:定义三个工具函数 --

def calculate(expression: str) -> str:
    """计算数学公式"""
    try:
        result = eval(expression)
        return str(result)
    except:
        return "计算错误"


def get_current_time() -> str:
    """获取当前时间"""
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())


def get_weather(city: str) -> str:
    """查询城市天气"""
    load_dotenv()
    api_key = os.getenv("API_KEY")
    api_host = os.getenv("API_HOST")
    headers = {"X-QW-Api-Key": api_key}

    try:
        with httpx.Client() as client:
            geo_url = f"https://{api_host}/geo/v2/city/lookup?location={city}"
            geo_resp = client.get(geo_url, headers=headers)
            city_id = geo_resp.json()["location"][0]["id"]

            weather_url = f"https://{api_host}/v7/weather/now?location={city_id}"
            weather_resp = client.get(weather_url, headers=headers)
            data = weather_resp.json()

        return f"{city}{data['now']['text']}{data['now']['temp']}°C,体感{data['now']['feelsLike']}°C"
    except:
        return "天气查询失败"


# -- 第 2 步:定义 tools JSON --

tools = [
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "计算数学公式,如 123*456 或 100+200",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "要计算的数学公式"
                    }
                },
                "required": ["expression"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "获取当前时间,如现在几点了",
            "parameters": {
                "type": "object",
                "properties": {}
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "查询城市天气,如北京今天天气怎么样",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "城市名称,如北京、上海"
                    }
                },
                "required": ["city"]
            }
        }
    }
]


# -- 第 3 步:初始化客户端 --

load_dotenv()
client = OpenAI(
    api_key=os.getenv("LLM_API_KEY"),
    base_url=os.getenv("LLM_BASE_URL")
)

# -- 第 4 步:交互循环 --

messages = [
    {"role": "system", "content": "你是一个智能助手,可以帮用户计算数学题、查询天气、查看当前时间。请用中文回答。"}
]

print("AI 助手(输入 q 退出)")
print("=" * 40)

while True:
    user_input = input("\n你:")
    if user_input == "q":
        print("再见!")
        break

    messages.append({"role": "user", "content": user_input})

    # 第一次调用 API:让大模型决定是否调用工具
    response = client.chat.completions.create(
        model="mimo-v2.5-pro",
        messages=messages,
        tools=tools
    )
    message = response.choices[0].message

    # 如果大模型要调用工具
    if message.tool_calls:
        messages.append(message)

        for tool_call in message.tool_calls:
            function_name = tool_call.function.name
            arguments = eval(tool_call.function.arguments)

            if function_name == "calculate":
                result = calculate(arguments["expression"])
            elif function_name == "get_weather":
                result = get_weather(arguments["city"])
            elif function_name == "get_current_time":
                result = get_current_time()
            else:
                result = "未知工具"

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "content": result
            })

        # 第二次调用 API:大模型基于工具结果生成自然语言回答
        final_response = client.chat.completions.create(
            model="mimo-v2.5-pro",
            messages=messages
        )
        print(f"AI:{final_response.choices[0].message.content}")
        messages.append(final_response.choices[0].message)

    # 如果大模型不需要调用工具(直接聊天)
    else:
        print(f"AI:{message.content}")
        messages.append(message)

完整数据流图

用户:"北京天气怎么样"
    ↓
messages.append({"role": "user", ...})
    ↓
第一次调 API:messages + tools → 发给大模型
    ↓
大模型返回:tool_calls = [{name: "get_weather", arguments: '{"city":"北京"}'}]
    ↓
你的代码读取:function_name = "get_weather",arguments = {"city": "北京"}
    ↓
if-elif 分发:get_weather("北京") → "北京:晴,25°C,体感27°C"
    ↓
messages.append({"role": "tool", "content": "北京:晴,25°C..."})
    ↓
第二次调 API:messages(含工具结果)→ 发给大模型
    ↓
大模型回答:"北京今天天气晴朗,温度25°C,体感27°C,适合出门!"
    ↓
用户看到最终回答

你踩过的坑

坑 1:required 放在了 properties 里面

# ❌ required 在 properties 里面,大模型收不到
"properties": {
    "expression": {"type": "string", "description": "..."},
    "required": ["expression"]    # 错!
}

# ✅ required 和 properties 同级
"properties": {
    "expression": {"type": "string", "description": "..."}
},
"required": ["expression"]    # 对!

坑 2:用户输入用了 “system” 角色

# ❌ 用户输入当成系统指令发了
messages.append({"role": "system", "content": user_input})

# ✅ 用户输入必须用 "user" 角色
messages.append({"role": "user", "content": user_input})

这是最关键的 bug。 用了 system 角色,大模型以为是"系统设定"而不是"用户问题",所以不调用工具,直接用聊天回答。

坑 3:message 写成了 messages(复数)

# ❌ messages(复数)不存在
message = response.choices[0].messages

# ✅ message(单数)
message = response.choices[0].message

坑 4:function_name 写成了 function.name

# ❌ 下划线写法(不存在)
function_name = tool_call.function_name

# ✅ 点号写法
function_name = tool_call.function.name

坑 5:arguments 读取错误

# ❌ function.parameters 不存在
arguments = eval(tool_call.function.parameters)

# ✅ 正确的属性名是 arguments
arguments = eval(tool_call.function.arguments)

坑 6:get_current_time 加了多余的参数

函数本身不需要参数,但 tools 里给它加了 expression 参数。正确写法是 properties: {} 空对象。

坑 7:get_weather 参数名写成 expression

函数签名是 def get_weather(city: str),参数叫 city。tools 里的参数名必须和函数签名一致。


你问过的好问题

Q1:tools JSON 里多个工具是增加 function 还是增加 tools?
→ 增加 tools 列表里的元素。tools 是工具箱,每个 {} 是一把工具。

Q2:tools 里的 expression 参数是从哪里拿到的?
→ 大模型自己从用户的问题里"理解"出来的。你在 description 里写清楚参数是什么意思,大模型就会自动填值。

Q3:if-elif 分发是在 while 循环外面还是里面?
→ 里面。while 外面是初始化(client、messages、tools),while 里面是每轮对话的完整流程。

Q4:geo_url 那种 URL 是固定的吗?
→ 不是 Python 语法,是和风天气官方 API 文档规定的地址。每个 API 都有自己的 URL 格式,看文档才知道怎么拼。

Q5:第一次调用 API 怎么知道返回数据的结构?
→ 先 print(data) 看原始 JSON 长什么样,再写提取代码。这是调任何新 API 的标准流程。


概念速查表

概念 一句话理解
Function Calling 让大模型能调用你写的 Python 函数
tools 工具箱,告诉大模型有哪些工具可用
name 工具名,必须和 Python 函数名一致
description 工具描述,大模型根据这个决定什么时候调用
properties 函数参数定义,告诉大模型需要传什么参数
required 哪些参数是必填的
tool_calls 大模型返回的调用指令
role: “user” 用户输入必须用这个角色
role: “system” 系统设定,不是用户问题
role: “tool” 工具执行结果

Day 1-13 串联回顾

Day 1-2   猜数字 / 学生管理      → Python 基础语法
Day 3     文件 I/O + JSON        → 数据持久化
Day 4     面向对象 class          → 代码结构化
Day 5     argparse               → 命令行参数
Day 6     API 调用               → 连接互联网
Day 7     异步编程               → 提升性能
Day 8     高级特性               → 代码简化
Day 9     类型提示 + Pydantic    → 数据校验
Day 10    FastAPI                → 变成 Web 服务
Day 11    LLM API + Prompt       → 调用大模型
Day 12    Function Calling       → 让大模型调用函数
Day 13    多工具 Agent           → 三个工具 + 多轮对话 ← 你在这里

从猜数字到多工具 AI Agent,你只用了 13 天。


一句话总结

多工具 Agent = 多个函数 + tools 列表 + if-elif 分发 + while 循环。

大模型是"大脑"(决定调哪个工具),你的代码是"手脚"(执行函数)。

Day 1-12 的知识全都用上了:循环判断、API 调用、时间模块、LLM API、Function Calling——Day 13 是所有技能的汇聚点。


本文是 Python 熟练度特训 Day 13 的笔记。Phase 2(AI 核心技能)进入 Agent 实战阶段。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐