AIGC宇宙 AIGC宇宙

一日一技:如何快速生成大模型工具调用的JSON Schema

作者:kingname
2025-04-27 07:57
在使用大模型的工具调用时,我们需要编写JSON Schema,例如下图的tools字段的值:图片这个Schema写起来非常麻烦,括号太多了,看着眼花。 不信你肉眼看看,你需要几秒钟才能分清楚type: "object"跟哪个字段在同一层级? 这个Schema有没有什么办法自动生成呢?

在使用大模型的工具调用时,我们需要编写JSON Schema,例如下图的tools字段的值:

图片图片

这个Schema写起来非常麻烦,括号太多了,看着眼花。不信你肉眼看看,你需要几秒钟才能分清楚type: "object"跟哪个字段在同一层级?这个Schema有没有什么办法自动生成呢?

LangChain提供了一个@tool装饰器来简化工具调用的JSON Schema,直接装饰函数就能使用了。例如:

复制
import json
from langchain_core.tools.convert import tool


@tool(parse_docstring=True)
def parse_user_info(name: str, age: int, salary: float) -> bool:
    """
    保存用户的个人信息
 
    Args:
        name: 用户名
        age: 用户的年龄
        salary: 用户的工资
    """
    return True

然后,我们可以通过打印函数名的.args_schema.model_json_schema()来获取到类似于Tool Calling的JSON Schema,如下图所示:

图片图片

这种方式有两个问题:

1. Tool Calling需要的JSON Schema中,参数名对应的字段应该是name,但这里导出来的是title。

2. 函数的docstring使用的是Google Style,跟Python的不一样。

在Python里面,我们写docstring时,一般这样写::param 参数名: 参数解释,例如下面这样:

复制
import json
from langchain_core.tools.convert import tool


@tool
def parse_user_info(name: str, age: int, salary: float) -> bool:
    """
    保存用户的个人信息
 
    :param name: 用户名 
    :param age: 用户的年龄
    :param salary: 用户的工资
    :return: bool,成功返回True,失败返回False
    """
    return True

schema = parse_user_info.args_schema.model_json_schema()
print(json.dumps(schema, ensure_ascii=False, indent=2))

但使用这种方式定义的时候,@tool装饰器不能加参数parse_docstring=True,否则会报错。可如果不加,提取的信息里面,字段没有描述。效果如下图所示:

图片图片

这两个问题,其实有一个通用的解决办法,那就是直接使用`Pydantic`。实际上,LangChain本身使用的也是Pydantic。如下图所示:

图片图片

我之前写过一篇文章:一日一技:如何使用大模型提取结构化数据,介绍了一个第三方库,名叫`instructor`。它本质上就是把Pydantic定义的类转成Tool Calling需要的JSON Schema,然后通过大模型的Tool Calling来提取参数。使用使用它,我们可以非常容易的实现本文的目的。

使用Pydantic定义我们要提取的数据并转换为JSON Schema格式:

复制
import json
from pydantic import BaseModel, Field

class UserInfo(BaseModel):
    """
    用户个人信息
    """
    name: str = Field(..., descriptinotallow='用户的姓名')
    age: int = Field(default=None, descriptinotallow='用户的年龄')
    salary: float = Field(default=None, descriptinotallow='用户的工资')

schema = UserInfo.model_json_schema()
print(json.dumps(schema, indent=2, ensure_ascii=False))

Field的第一个参数如果是三个点...,表示这个字段是必填字段。如果想把一个字段设定为可选字段,那么Field加上参数default=None。

运行效果如下图所示:

图片图片

参数描述直接写到参数字段定义里面,根本不需要担心注释格式导致参数没有描述,管他是Google Style还是Python Style。

接下来,我们要把Pydantic输出的这个格式转换为Tool Calling需要的JSON Schema格式。我们来看一下Instructor的源代码:

图片图片

把他这个代码复制出来,用来处理刚刚Pydantic生成的JSON Schema:

复制
from docstring_parser import parse


def generate_tool_calling_schema(cls):
    schema = cls.model_json_schema()
    docstring = parse(cls.__doc__ or'')
    parameters = {
        k: v for k, v in schema.items() if k notin ("title", "description")
    }
    for param in docstring.params:
        if (name := param.arg_name) in parameters["properties"] and (
            description := param.description
        ):
            if"description"notin parameters["properties"][name]:
                parameters["properties"][name]["description"] = description
    
    parameters["required"] = sorted(
        k for k, v in parameters["properties"].items() if"default"notin v
    )
    
    if"description"notin schema:
        if docstring.short_description:
            schema["description"] = docstring.short_description
        else:
            schema["description"] = (
                f"Correctly extracted `{cls.__name__}` with all "
                f"the required parameters with correct types"
            )
    
    return {
        "name": schema["title"],
        "description": schema["description"],
        "parameters": parameters,
    }

这里依赖一个第三方库,叫做docstring_parser,这个库的原理非常简单,就是正则表达处理docstring而已。大家甚至可以看一下他的源代码然后自己实现。

运行以后效果如下图所示。

图片图片

注意在参数信息里面,会有'default': null和title字段,这两个字段即使传给大模型也没有关系,它会自动忽略。如果大家觉得他们比较碍眼,也可以改动一下代码,实现跟Tool Calling 的JSON Schema完全一样:

复制
from docstring_parser import parse


def generate_tool_calling_schema(cls):
    schema = cls.model_json_schema()
    docstring = parse(cls.__doc__ or'')
    parameters = {
        k: v for k, v in schema.items() if k notin ("title", "description")
    }
    for param in docstring.params:
        if (name := param.arg_name) in parameters["properties"] and (
            description := param.description
        ):
            if"description"notin parameters["properties"][name]:
                parameters["properties"][name]["description"] = description

    parameters["required"] = sorted(
        k for k, v in parameters["properties"].items() if"default"notin v
    )

    for prop_name, prop_schema in parameters["properties"].items():
        prop_schema.pop("default", None)
        prop_schema.pop('title', None)

    if"description"notin schema:
        if docstring.short_description:
            schema["description"] = docstring.short_description
        else:
            schema["description"] = (
                f"Correctly extracted `{cls.__name__}` with all "
                f"the required parameters with correct types"
            )

    # 按 Tool Calling 规范封装:
    return {
        "type": "function",
        "function": {
            "name": schema["title"],
            "description": schema["description"],
            "parameters": parameters,
        }
    }

运行效果如下图所示:

图片图片

最后给大家出个思考题:如果函数的参数包含嵌套参数,应该怎么处理?

相关标签:

相关资讯

OpenAI GPT-4o 新版本突然上线:丝滑解决 9.11 和 9.9 谁大,更强也更便宜了

GPT-4o 新版本突然上线,更强更便宜。能力全方位提升,ZeroEval 基准测试直接跃居第一。输入和输出分别节省 50%、33%。四舍五入就是 API 降价啊。token 输出扩展到 16k,此前支持 4k。9.11 和 9.9 谁大这个问题,也能丝滑解决。这是因为 OpenAI 给 API 中引入了结构化输出。通过 JSON 模式确保模型输出符合开发者定义的结构,能让模型变得更可靠安全。最新版模型“GPT-4o-2024-08-06”在 JSON 模式评估中,得分 100%。相比之下,去年 6 月的版本得分还
8/7/2024 1:13:07 PM
汪淼

1分钟学会DeepSeek本地部署,小白也能搞定!

DeepSeek 是国内顶尖 AI 团队「深度求索」开发的多模态大模型,具备数学推理、代码生成等深度能力,堪称"AI界的六边形战士"。 DeepSeek 身上的标签有很多,其中最具代表性的标签有以下两个:低成本(不挑硬件、开源)高性能(推理能力极强、回答准确)一、为什么要部署本地DeepSeek? 相信大家在使用 DeepSeek 时都会遇到这样的问题:图片这是由于 DeepSeek 大火之后访问量比较大,再加上漂亮国大规模、持续的恶意攻击,导致 DeepSeek 的服务器很不稳定。
2/10/2025 12:00:10 AM

6周搞定18个月的工作量,爱彼迎不满老工具,暴力循环大模型怒迁代码,效果出奇好,怎么回事?谷歌亚马逊也做过类似的事

编辑 | 云昭Anthropic首席执行官Dario时不时就会出来发声,声称人工智能即将取代人工编码,或者抛出一个惊人的数字,预测在短短6个月内将有90%的编码工作将被AI取代。 这种措辞无疑有夸大的成分,但这里想说的是,这并非空穴来风,或许6个月内AI不会取代90%的程序员,但取代90%的编程工作不无可能! 因为企业接纳和适配大模型的速度远比我们想象得要快!
4/18/2025 4:05:39 PM
云昭
  • 1