GraphQL 与 LLM/Agent 集成:工具调用、结构化输出与 MCP 对比

GraphQL 与 LLM/Agent 集成深度:Function Calling 工具定义、结构化输出、Schema 作为工具契约、GraphQL MCP Server 与 MCP 协议对比,帮助 AI 应用安全高效地调用数据层。

大语言模型(LLM)与 Agent 的崛起,让「对话即接口」成为新的交互范式。然而模型本身并不知道你的数据库在哪里、有哪些表、字段叫什么。把 GraphQL 的强类型 Schema 变成 LLM 可以调用的工具契约,让模型像调用函数一样查询真实业务数据——这正是本文要解决的核心问题。我们将从 Function Calling 的工具定义出发,讨论结构化输出、Schema 裁剪、GraphQL MCP Server 与 MCP 协议对比,帮助你构建一个既安全又高效的 AI 数据访问层。

一、为什么 LLM 需要 GraphQL

1.1 模型与数据的鸿沟

LLM 的训练数据是互联网文本,而你的业务数据存在私有数据库里。让 Agent 直接写 SQL 访问数据库极其危险——它会构造任意查询、可能拖垮库、更可能越权读取。GraphQL 恰好提供了一层「类型安全 + 权限可控 + 字段可选」的中间契约:

  • 字段级白名单:模型只能请求 Schema 里暴露的字段。
  • 参数校验:ID!、Int 等类型在进入 resolver 前就被校验。
  • 授权挂钩:每个 resolver 都可以注入权限判断,基于调用者身份裁剪数据。

1.2 Schema 就是工具的天然描述

工具调用(Function/Tool Calling)要求把每个可用操作描述成「名称 + 描述 + 参数 JSON Schema」。GraphQL 的 introspection 结果本质上就是一份完整的 JSON Schema 描述:类型、字段、参数、非空约束一应俱全。把 introspection 转成 LLM 工具定义,几乎是零成本的映射。

二、Function Calling 工具定义

2.1 把 GraphQL 查询转成工具

主流 LLM 的工具协议(OpenAI Function Calling、Anthropic Tool Use)都接受 JSON Schema 格式的参数定义。以下展示如何把一条 GraphQL 查询描述为工具:

{
  "name": "search_products",
  "description": "根据关键词搜索商品,返回标题、价格与库存。适合回答商品查询类问题。",
  "input_schema": {
    "type": "object",
    "properties": {
      "keyword": { "type": "string", "description": "搜索关键词" },
      "first": { "type": "integer", "description": "返回条数,默认 10" }
    },
    "required": ["keyword"]
  }
}

服务端收到模型发出的工具调用后,将参数映射到 GraphQL 查询并执行:

const toolResult = await client.request(`
  query SearchProducts($keyword: String!, $first: Int) {
    searchProducts(keyword: $keyword, first: $first) {
      id title price stock
    }
  }
`, { keyword: args.keyword, first: args.first ?? 10 });

2.2 从 introspection 自动生成工具

手写每个工具既繁琐又易漂移。借助 @graphql-tools 的打印工具,可以从 Schema 自动生成工具清单:

import { printSchema, lexicographicSortSchema } from 'graphql';

const schemaSDL = printSchema(lexicographicSortSchema(schema));
// 将 SDL 发给模型,或进一步用 JSON Schema 生成器把类型转成工具定义

实践中推荐「显式工具白名单」而非「全量暴露」:为 Agent 精选 10~20 个高频、低风险的工具(如 getUser、searchArticles、listOrders),而不是把整个 Schema 的每个查询都注册成工具。工具越少,模型越不容易选错。

2.3 工具描述的质量决定成功率

LLM 在「选哪个工具」上的准确率,与工具描述的质量强相关:

  • 描述里写明用途与触发条件:「当用户询问天气时使用此工具」比「获取天气」更易命中。
  • 参数描述标明格式:日期参数写明 YYYY-MM-DD,ID 参数标明是整数还是 UUID。
  • 字段说明标注成本:对昂贵字段(如需要调用推荐算法)标注「高成本,仅在必要时请求」。

三、结构化输出:让模型查询更可靠

3.1 约束输出的两种方式

Agent 调用 GraphQL 后,模型的后续推理依赖返回数据。为了让模型「看懂」结果,结构化输出至关重要,主要有两条路径。

路径一:工具结果的 JSON 直接回填。工具调用返回的 data 本身就是结构化 JSON,模型无需额外解析。

{
  "search_products": {
    "data": [
      { "id": "p1", "title": "手冲咖啡壶", "price": 299, "stock": 12 }
    ],
    "errors": null
  }
}

路径二:GraphQL 响应 Schema 化。使用 graphql-json-schema 之类的库把类型转成 JSON Schema,作为模型输出的校验器:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string" },
          "price": { "type": "number" }
        },
        "required": ["id", "title"]
      }
    }
  }
}

3.2 错误语义化

LLM 面对 GraphQL 错误数组时往往不知所措。服务端应把错误转成模型友好的格式:

{
  "data": null,
  "errors": [
    {
      "message": "商品 p_not_exist 不存在",
      "extensions": { "code": "NOT_FOUND", "path": ["searchProducts"] }
    }
  ]
}

在把结果回填给模型之前,将 extensions.code 归一化为 NOT_FOUND/UNAUTHORIZED/RATE_LIMITED/BAD_INPUT 枚举,并附一句可执行的修复建议,能显著提升 Agent 多轮重试的成功率。

3.3 截断与分页

模型上下文有限,工具返回超大结果会被截断甚至污染推理。对列表型工具强制 first 参数,并把响应封装为「前 N 条摘要 + totalCount」:

query ListOrders($first: Int! = 5, $after: String) {
  orders(first: $first, after: $after) {
    edges { node { id status total } }
    pageInfo { hasNextPage endCursor }
  }
}

服务端在工具执行层统一注入 first <= 20 的硬上限,防止模型构造出一次性拉取全表的查询。

四、Schema 裁剪与安全边界

4.1 面向 Agent 的 Schema 裁剪

直接把生产 Schema 暴露给模型,意味着模型能看到内部字段(如 internalScore、rawSql),也可能触发昂贵的计算字段。应构建一个独立的 Agent Schema:

# agent-schema.graphql —— 仅供 LLM 工具调用
type Query {
  searchProducts(keyword: String!, first: Int = 10): [Product!]!
  product(id: ID!): Product
  article(id: ID!): Article
  articlesByTag(tag: String!, first: Int = 10): [Article!]!
}

type Product {
  id: ID!
  title: String!
  price: Float!
  stock: Int!
}

type Article {
  id: ID!
  title: String!
  summary: String!
  publishedAt: String!
}

裁剪原则:只保留 Agent 高频需要的读操作,去除写操作(除非专门设计工具)、去除内部字段、限制分页上限。Schema 裁剪层可以使用 @graphql-tools 的 filterSchema,也可以直接新建一个子图作为 Agent BFF。

4.2 权限与租户隔离

Agent 调用本质上是「无登录态的程序化调用」,权限必须显式注入:

  • 通过请求头注入 x-agent-id 与 x-tenant-id,resolver 在 context 中解析。
  • 所有查询强制带上租户过滤,防止模型跨租户读取数据。
  • 对敏感字段(手机号、邮箱、账单)在 Agent Schema 中直接移除,而不是依赖权限过滤。
const context = ({ req }) => ({
  agentId: req.headers['x-agent-id'],
  tenantId: req.headers['x-tenant-id'],
  isAgentCall: true,
});

const resolvers = {
  Query: {
    orders: (_, args, ctx) => {
      // Agent 调用永远只查本租户
      return db.orders.where({ tenantId: ctx.tenantId }).first(args.first);
    },
  },
};

4.3 查询成本控制

LLM 生成的查询可能很深、别名很多、列表很大。对 Agent 流量应启用比人类客户端更严格的限制:

  • 深度限制:max_depth: 8。
  • 别名限制:max_aliases: 10,防止模型构造重复字段放大响应。
  • 复杂度上限:对列表字段乘以权重,超过阈值拒绝执行并返回可读错误。

五、GraphQL MCP Server:标准化的工具桥

5.1 MCP 是什么

MCP(Model Context Protocol)是 Anthropic 于 2024 年底开源的开放协议,目标是统一「LLM 访问外部工具/数据」的标准。MCP Server 暴露三类原语:Tools(工具)、Resources(资源)、Prompts(提示模板)。客户端(Claude Desktop、Claude Code、各类 SDK)通过标准化的 JSON-RPC 传输层发现并调用这些能力。

5.2 官方 GraphQL MCP Server

MCP 生态中已有 @modelcontextprotocol/server-graphql 等官方参考实现,其核心思路是:把 introspection 结果转成 MCP Tools,每个查询字段对应一个工具,参数映射为 JSON Schema。

{
  "tools": [
    {
      "name": "query_users",
      "description": "执行 GraphQL 查询 users",
      "inputSchema": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "first": { "type": "integer", "default": 10 }
        }
      }
    }
  ]
}

MCP Server 通常运行在独立进程中,通过 stdio 或 SSE/HTTP 传输与 LLM 客户端通信。GraphQL 服务只需提供 introspection 即可被桥接,无需改造原有 API。

5.3 自定义 GraphQL MCP Server

以 TypeScript 实现一个把 GraphQL 包装为 MCP Tools 的最小服务:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { fetch } from 'undici';

const server = new McpServer({ name: 'graphql-gateway', version: '1.0.0' });

server.tool(
  'searchProducts',
  '按关键词搜索商品',
  { keyword: 'string', first: 'number?' },
  async ({ keyword, first }) => {
    const res = await fetch('https://api.example.com/graphql', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'x-agent-id': 'demo' },
      body: JSON.stringify({
        query: `query($k: String!, $f: Int) {
          searchProducts(keyword: $k, first: $f) { id title price }
        }`,
        variables: { k: keyword, f: first ?? 10 },
      }),
    });
    const json = await res.json();
    return { content: [{ type: 'text', text: JSON.stringify(json.data) }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

MCP 的标准化收益在于:同一个 GraphQL 服务可以同时服务于 Claude、其他 Agent 框架与自研编排器,工具描述、参数校验、传输协议都是统一的。

六、GraphQL 直接调用 vs MCP 对比

维度GraphQL 直接调用MCP(GraphQL MCP Server)
契约来源GraphQL Schema(字段/类型/参数)MCP 工具描述(基于 introspection 生成)
协议HTTP POST/GET + JSONJSON-RPC over stdio/SSE/HTTP
发现机制手动注册工具或 introspectionMCP Server 自动暴露工具清单
权限模型复用 GraphQL resolver 授权在 MCP 层注入代理身份,转发给 GraphQL
适用场景自研 Agent、受控工具集多客户端接入、标准化工具生态
灵活性查询语言完全自由工具粒度较粗,往往按查询字段包装
治理需要自己做 Schema 裁剪与限流MCP Server 层可集中做裁剪与审计

选型建议:如果 Agent 是你自己写的、工具集固定,直接调 GraphQL 更灵活高效;如果希望开放给多种 MCP 客户端、降低接入成本,用 MCP Server 包装 GraphQL 更合适。两者本质上是「协议层」与「数据层」的分工:MCP 管工具编排,GraphQL 管数据契约。

七、生产实践:Agent 数据访问层架构

7.1 推荐架构

LLM 客户端 / Agent
   │  Function Calling / Tool Use
   ▼
工具编排层(选工具、填参数、解析结果、错误重试)
   │  标准 JSON
   ▼
Agent BFF(Schema 裁剪、权限注入、分页上限、成本限制)
   │  GraphQL over HTTP
   ▼
GraphQL 网关(认证、限流、日志、观测)
   │
   ▼
子图 / 数据源(users、orders、products ...)

7.2 关键落地清单

  1. 裁剪:为 Agent 建独立 Schema,删除内部与敏感字段。
  2. 白名单:只注册 10~20 个精选工具,不要全量暴露。
  3. 注入身份:所有 Agent 请求带 x-agent-id,权限与审计都挂在它上面。
  4. 硬上限:first 上限、深度上限、复杂度上限三层叠加。
  5. 错误归一化:把 GraphQL errors 转成模型可读的枚举与建议。
  6. 观测:记录每次工具调用的输入输出、token 消耗与延迟,评估工具成功率。

7.3 成本与 token 控制

Agent 场景的 token 成本集中在「工具描述 + 返回数据」重复进上下文。优化手段:

  • 工具描述精简:字段级描述只写最必要的。
  • 返回裁剪:只返回模型推理需要的字段,去掉大文本正文。
  • 缓存:对高频查询(如商品价格)用短期缓存,减少真实 DB 调用与重复返回。

八、一句话总结

GraphQL 的强类型 Schema 天然适合作为 LLM 的工具契约——通过 Function Calling 转成工具定义、用结构化输出保证可靠性、以 MCP 协议标准化接入,配合 Schema 裁剪与权限注入,就能构建一个安全高效的 AI 数据访问层。

FAQ

Q1: 让 LLM 直接访问 GraphQL Schema,安全性如何保障?

A: 不建议直接暴露生产 Schema。应构建独立的 Agent Schema,只保留白名单读操作;在 Agent BFF 层注入代理身份(agent-id + tenant-id),强制租户过滤;对敏感字段直接移除而非依赖权限。GraphQL 的类型系统与 resolver 授权是第二道防线,但工具层裁剪才是第一道。

Q2: Function Calling 与 MCP 是什么关系?

A: Function Calling 是 LLM 供应商(OpenAI、Anthropic 等)定义的单模型工具协议,描述「工具长什么样」;MCP 是跨供应商、跨客户端的开放协议,管理「工具如何被发现与调用」。GraphQL MCP Server 可以把 introspection 转成 MCP 工具,从而让同一个 GraphQL 服务服务多种 Agent 客户端。

Q3: 如何防止 LLM 构造超深查询拖垮服务?

A: 三管齐下:深度限制(如 8 层)、别名限制(如 10 个)、复杂度分析(列表字段乘以权重)。对 Agent 流量的限制应严于人类客户端,超限时返回模型可读的错误码(如 QUERY_TOO_COMPLEX)而非静默截断。

Q4: 工具返回的数据太大,污染模型上下文怎么办?

A: 强制 first 上限(如 20),返回「前 N 条 + totalCount」摘要;对大文本字段(正文、日志)在 Agent Schema 中替换为摘要字段;对高频结果启用短期缓存。必要时把长数据写进附件/文件而非对话上下文。

Q5: 自研 Agent 场景下,直接调 GraphQL 和走 MCP 哪个更好?

A: 自研且工具集固定的场景直接调 GraphQL 更灵活——查询语言自由、工具粒度精确、权限复用 resolver 授权。MCP 的价值在于多客户端标准化接入。成熟团队常用「GraphQL 数据层 + 工具编排层」的组合,MCP 只是可选的协议适配壳。

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「GraphQL」更多文章

  1. GraphQL BFF 与微前端:多前端团队的 Schema 分片与协作模式
  2. GraphQL 限流与成本控制:查询成本分析、复杂度限制与按量计费
  3. GraphQL 边缘缓存与 CDN:POST 缓存、边缘执行与缓存键设计