A2API 开源:让 AI 只生成一次任务 UI,后续数据操作直接走 API

2026-07-29 20 预计阅读时间: 1 分钟
来源: oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

AI Agent 读写业务数据时,一个长期存在的矛盾是:自然语言足够灵活,但每次请求都交给大模型解释,会引入延迟、成本和不确定性。A2API 给出的思路是把 AI 放在任务的“编译阶段”:借助 A2UI 从对话生成任务 UI 和 API 调用结构,之后用户修改筛选、排序、分页或表单字段时,客户端直接调用 HTTP API,不再重复请求大语言模型。

这不是让模型拥有更大的数据库权限,而是缩短模型参与数据链路的时间,并把持续执行交还给可校验、可审计的 API。对于表格、表单、图表以及常规增删改查场景,这种边界尤其重要。

从“每次推理”改成“生成一次,执行多次”

传统的对话式数据 Agent 往往把每一步都交给模型:用户提出问题,模型生成查询;用户换一个排序条件,模型再次生成查询;翻到下一页,又进行一次推理。模型处在每次数据访问的关键路径上。

A2API 所描述的流程可以拆成两个阶段:

  1. 生成阶段:AI 根据用户意图生成任务 UI,以及 UI 控件和 HTTP API 参数之间的映射。
  2. 执行阶段:筛选、排序、分页和增删改查操作直接转换成 HTTP 请求,由 API 服务稳定执行。

例如,一个“查看待处理订单”的需求可以生成包含状态筛选框、排序选择器、分页器和表格的界面。生成完成后,用户从“待处理”切换到“已完成”,只需要把 status 参数从 pending 改为 completed,没有必要再次让模型理解整个任务。

这相当于让 AI 生成一个受约束的任务客户端。模型负责把模糊意图变成结构化交互,API 负责持续处理确定性请求。

可靠性来自协议边界,而不是更长的提示词

“AI 生成一次”并不自动等于安全。真正决定系统能否进入生产环境的,是生成结果能否被限制在明确的协议和权限边界内。结合腾讯 APIJSON 生态协议,可以重点检查以下几层。

  • 资源白名单:只允许访问任务需要的表、视图或业务 API,不能让模型自由拼接任意资源名。
  • 字段白名单:读取接口限制可返回字段,写入接口限制可修改字段,避免越权读取和批量赋值漏洞。
  • 操作白名单:分别授权查询、创建、更新、删除;删除和批量更新应采用更严格的策略。
  • 参数校验:分页大小、排序字段、过滤操作符和数据类型都由服务端验证,不能信任生成的 UI。
  • 身份与审计:HTTP API 仍然根据当前用户鉴权,并记录操作者、请求参数、目标资源和执行结果。

关键原则是:UI Schema 可以由 AI 生成,但权限不能由 AI 生成。客户端隐藏按钮也不是安全控制,服务端必须对每次请求重新鉴权。

可以这样实践:做一个“编译后直连 API”的最小原型

下面是一个可运行的演示。它不声称复现 A2API 或 APIJSON 的具体接口,而是用 Python 标准库模拟同一种执行模式:AI 阶段产出固定 UI Schema,后续筛选、排序、分页全部直接调用 HTTP API。

将以下内容保存为 server.py,要求 Python 3.10 或更高版本:

from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs, urlparse
import json

ORDERS = [
    {"id": 1, "customer": "Acme", "status": "pending", "amount": 1200},
    {"id": 2, "customer": "Globex", "status": "completed", "amount": 860},
    {"id": 3, "customer": "Initech", "status": "pending", "amount": 2400},
    {"id": 4, "customer": "Umbrella", "status": "completed", "amount": 1800},
]

ALLOWED_SORTS = {"id", "customer", "amount"}
ALLOWED_STATUS = {"pending", "completed"}

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        parsed = urlparse(self.path)
        if parsed.path != "/api/orders":
            self.send_error(404)
            return

        query = parse_qs(parsed.query)
        status = query.get("status", ["pending"])[0]
        sort = query.get("sort", ["id"])[0]

        try:
            page = max(1, int(query.get("page", ["1"])[0]))
            page_size = min(50, max(1, int(query.get("page_size", ["2"])[0])))
        except ValueError:
            self.send_json(400, {"error": "page and page_size must be integers"})
            return

        if status not in ALLOWED_STATUS or sort not in ALLOWED_SORTS:
            self.send_json(400, {"error": "unsupported filter or sort field"})
            return

        rows = sorted(
            (row for row in ORDERS if row["status"] == status),
            key=lambda row: row[sort],
        )
        start = (page - 1) * page_size
        result = {
            "items": rows[start:start + page_size],
            "page": page,
            "page_size": page_size,
            "total": len(rows),
        }
        self.send_json(200, result)

    def send_json(self, status, payload):
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def log_message(self, fmt, *args):
        print(fmt % args)

HTTPServer(("127.0.0.1", 8080), Handler).serve_forever()

启动服务:

python3 server.py

在另一个终端模拟 UI 的首次查询:

curl --get 'http://127.0.0.1:8080/api/orders' \
  --data-urlencode 'status=pending' \
  --data-urlencode 'sort=amount' \
  --data-urlencode 'page=1' \
  --data-urlencode 'page_size=2'

用户切换筛选条件时,前端只需发出新的确定性请求:

curl --get 'http://127.0.0.1:8080/api/orders' \
  --data-urlencode 'status=completed' \
  --data-urlencode 'sort=id' \
  --data-urlencode 'page=1' \
  --data-urlencode 'page_size=20'

对应的任务 UI Schema 可以这样设计。以下字段是假设性的项目约定,需要根据实际 A2UI/A2API 实现调整:

{
  "task": "order-list",
  "endpoint": "/api/orders",
  "method": "GET",
  "controls": [
    {
      "type": "select",
      "name": "status",
      "options": ["pending", "completed"]
    },
    {
      "type": "select",
      "name": "sort",
      "options": ["id", "customer", "amount"]
    },
    {
      "type": "pagination",
      "pageParam": "page",
      "sizeParam": "page_size",
      "maxSize": 50
    }
  ],
  "result": {
    "type": "table",
    "fields": ["id", "customer", "status", "amount"]
  }
}

这个 Schema 的价值不是描述页面颜色或间距,而是固定可调用的端点、方法、参数和结果字段。生产实现还应对 Schema 做签名或版本校验,防止客户端把已审核的查询界面篡改成写入或删除请求。

接入现有系统时应保留三道闸门

A2API 这类架构适合参数明确、交互频繁的业务任务,例如后台列表、运营报表、审批表单和资源管理。接入时可以按以下顺序推进:

  1. 从只读查询开始,开放有限的数据集、筛选条件和排序字段。
  2. 对 AI 生成的 UI Schema 做机器校验,并在首次发布前增加人工确认。
  3. 将鉴权、字段过滤、限流和审计放在 API 服务或网关,而不是生成页面中。
  4. 更新和删除操作增加幂等键、版本号或二次确认,避免重复提交与并发覆盖。
  5. 保存 Schema 版本、API 版本和请求日志,确保线上问题可以复现。
  6. 当用户意图超出已生成 UI 的能力范围时,再回到 AI 重新生成任务,而不是让客户端偷偷扩展请求。

这种模式的主要收益,是把大模型从高频执行路径移到低频生成路径。代价也很明确:团队需要维护 UI Schema、API 契约、版本兼容和服务端策略。对于结构稳定、调用频繁的数据任务,这笔工程投入通常更容易衡量;对于一次性探索或高度开放的问题,持续对话式 Agent 仍可能更合适。


相关推荐