1. 项目概述:当OpenAPI遇上LLM,如何让HTTP后端“听懂”AI指令
如果你正在构建一个AI应用,并且想让大语言模型(LLM)能够直接调用你现有的HTTP API,你可能会立刻想到一个词:Function Calling(函数调用)。无论是OpenAI的GPT、Anthropic的Claude,还是阿里云的Qwen,主流LLM都提供了让模型根据描述,结构化地调用外部工具的能力。但问题来了,你的后端可能已经有几十甚至上百个API接口,每个接口都有复杂的参数和响应结构。难道要你手动为每个接口写一份LLM能理解的函数描述吗?这工作量想想就让人头大。
这正是@samchon/openapi(现已合并至typia项目)要解决的核心痛点。它不是一个全新的框架,而是一个智能的“翻译官”。它的任务非常明确:将你现有的、符合OpenAPI/Swagger规范的API文档,自动、精准地转换为各大LLM厂商都认的Function Calling Schema。这意味着,你无需重写业务逻辑,也无需手动维护另一套AI接口描述,你的整个HTTP后端,可以近乎零成本地变成一个“AI可调用”的服务。
我最初接触这个工具,是在为一个电商系统集成AI客服助手时。我们有一个庞大的商品、订单、用户管理系统,后端基于NestJS开发,已经用@nestjs/swagger生成了完整的OpenAPI 3.0文档。当产品经理提出“让AI能帮用户查订单、退换货”的需求时,我的第一反应是:难道要为这几十个相关接口,一个个去写Prompt,定义参数类型?这不仅是重复劳动,更可怕的是,一旦后端API有变动,AI侧的描述还得手动同步,维护成本会指数级上升。
@samchon/openapi的出现,让这个流程变得异常简单。它的工作流可以概括为三步:加载你的Swagger/OpenAPI文档 -> 将其转换为一个内部统一的“修正版”格式 -> 基于此格式生成LLM函数调用应用。这个过程中,它充分利用了typia这个强大的TypeScript运行时类型校验库,确保了从OpenAPI的JSON Schema到TypeScript类型,再到LLM函数参数验证的全链路类型安全。
简单来说,它把OpenAPI这种面向机器(和人类开发者)的接口描述语言,“编译”成了LLM能理解和执行的“操作手册”。这背后是一套对OpenAPI规范深刻的解构与重构,我们接下来会详细拆解。
2. 核心设计思路:为什么需要“修正版”OpenAPI?
在深入代码之前,我们必须先理解一个核心概念:OpenAPI v3.1 (emended),即“修正版”OpenAPI规范。这是@samchon/openapi能够可靠工作的基石。你可能会问,OpenAPI本身不就是标准吗?为什么还需要一个“修正版”?
2.1 OpenAPI规范的“模糊地带”与挑战
OpenAPI规范(以及其前身Swagger)旨在描述RESTful API,但它本身在定义JSON Schema时,存在一些历史遗留的模糊性和不一致之处。这些模糊性对于人类开发者阅读文档或许影响不大,但对于需要精确、无歧义地生成代码或进行自动化处理的工具(尤其是AI)来说,就是灾难。
举个例子,在OpenAPI中,一个属性是否可以同时为null和其他类型?早期的Swagger 2.0和OpenAPI 3.0使用nullable: true字段。而OpenAPI 3.1更贴近标准的JSON Schema Draft 2020-12,它建议使用type: ["string", "null"]这样的数组来表示。一个工具如果同时要处理不同版本的文档,就需要处理多种表达“可为空”的方式。
再比如,如何描述一个元组(Tuple)?是使用items是一个Schema对象的数组,还是使用prefixItems?不同版本、不同工具的理解可能不同。当LLM试图根据这些模糊的Schema去生成参数时,就很容易产生格式错误。
@samchon/openapi的“修正版”规范,就是为了消除这些歧义,建立一个唯一、确定的中间表示。它将所有不同版本的OpenAPI/Swagger文档,都先升级或修正到这个统一的中间格式,然后再基于这个“干净”的格式进行后续操作(如生成LLM Schema或降级回其他版本)。
2.2 “修正版”的核心改进
这个“修正版”规范主要做了以下几件事,让数据变得对AI更友好:
操作(Operation)合并与引用解析:在OpenAPI中,一个路径(Path)下的参数可以在路径级别和操作(如GET、POST)级别分别定义,并且可以使用
$ref引用其他地方的组件。修正版会将这些分散的参数全部合并到具体的操作对象中,并完全解析所有$ref引用,生成一个“扁平化”、自包含的操作定义。这样,每个HTTP端点对应哪个函数、需要哪些参数,就变得一目了然。JSON Schema的统一与简化:
- 消除混合类型:强制要求
type字段是单一字符串(如"string")或一个明确的字符串数组(如["string", "number"]),而不是一个可能包含其他杂质的对象。 - 统一空值处理:将所有形式的“可为空”表示法,统一为一种内部格式,避免后续处理时的分支判断。
- 标准化数组与元组:明确区分普通数组(所有元素类型相同)和元组(每个位置类型固定),并使用确定的字段来描述它们。
- 消除混合类型:强制要求
模式组合的整合:OpenAPI支持
anyOf、oneOf、allOf来组合多个Schema。修正版会尝试将这些组合模式“展平”或转化为更简单的结构,减少嵌套,让生成的LLM函数参数Schema更直观。
实操心得:这个设计非常巧妙。它没有尝试去修改或挑战OpenAPI标准,而是建立了一个内部的“清洁层”。所有外部的、可能“脏”的数据,都先经过这个清洁层处理,变成规整的、确定的数据结构。后续所有功能(LLM转换、验证、执行)都基于这个清洁层工作,极大地降低了内部逻辑的复杂度,也提高了最终输出给LLM的Schema的质量和一致性。这就像是一个翻译,先把各种方言统一翻译成标准普通话,再进行后续的交流。
3. 从零开始:快速集成与实战演练
理论讲完了,我们直接上手。假设你有一个现有的NestJS项目,并且已经通过@nestjs/swagger生成了swagger.json文件。我们的目标是将这个文件里的所有API,变成GPT-4o可以调用的函数。
3.1 基础安装与转换
首先,安装核心库。由于原@samchon/openapi已归档,功能合并到typia,我们直接安装typia。
npm install typia # 或者使用你喜欢的包管理器 # yarn add typia # pnpm add typia接下来,我们创建一个简单的脚本文件,比如llm-integration.ts。
// llm-integration.ts import { HttpLlm, OpenApi } from "@typia/utils"; import * as fs from 'fs/promises'; import OpenAI from 'openai'; // 1. 加载你的OpenAPI文档 async function main() { const swaggerJson = await fs.readFile('./swagger.json', 'utf-8'); const rawDocument = JSON.parse(swaggerJson); // 2. 转换为修正版OpenAPI文档 // `OpenApi.convert` 方法会自动识别Swagger 2.0, OpenAPI 3.0, 3.1 const emendedDocument: OpenApi.IDocument = OpenApi.convert(rawDocument); console.log(`成功转换文档,包含 ${Object.keys(emendedDocument.paths).length} 个路径`); // 3. 生成LLM函数调用应用 const llmApp: IHttpLlmApplication = HttpLlm.application({ document: emendedDocument, // 可选:配置函数名生成策略、参数过滤等 // naming: (path, method) => `${method}_${path.replace(/\//g, '_')}`, // separate: ... // 后续会讲 }); console.log(`生成了 ${llmApp.functions.length} 个LLM可调用函数`); // 4. 让我们看看第一个函数长什么样 const firstFunc = llmApp.functions[0]; if (firstFunc) { console.log('函数名:', firstFunc.name); console.log('描述:', firstFunc.description); console.log('方法:', firstFunc.method); console.log('路径:', firstFunc.path); console.log('参数Schema预览:', JSON.stringify(firstFunc.parameters, null, 2).substring(0, 500) + '...'); } // 后续步骤:初始化OpenAI客户端并使用这些函数 const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); // ... 与LLM交互的代码将在下一节展开 } main().catch(console.error);运行这个脚本 (npx tsx llm-integration.ts),你应该能看到你的API被成功转换成了一个个函数对象。每个IHttpLlmFunction对象都包含了LLM Function Calling所需的核心三要素:name(函数名)、description(描述)、parameters(符合JSON Schema的参数定义)。
注意事项:
- 路径冲突处理:如果你的OpenAPI文档中有多个路径模板映射到同一个操作(比如
/users/{id}和/users/me都指向同一个控制器方法),转换工具可能需要额外的配置来区分它们,或者你需要确保文档本身没有歧义。 - 复杂Schema支持:确保你的OpenAPI文档中使用的JSON Schema特性在
typia的支持范围内。typia对TypeScript类型的支持极其广泛,但如果你在文档中使用了非常冷门的JSON Schema关键字,转换时可能会被简化或忽略。建议先用typia.validate对原始文档做一次校验。
3.2 与LLM提供商集成:以OpenAI为例
现在,我们有了函数列表,下一步就是让LLM认识它们。这里以OpenAI的Node.js SDK为例,其他提供商(Claude, Qwen等)的调用方式大同小异。
// 接上面的 main 函数 // 5. 准备与LLM的对话 const availableFunctions = llmApp.functions.slice(0, 10); // 示例:先取前10个函数,避免上下文过长 const tools = availableFunctions.map(func => ({ type: 'function' as const, function: { name: func.name, description: func.description, parameters: func.parameters, } })); const messages: OpenAI.ChatCompletionMessageParam[] = [ { role: 'system', content: '你是一个智能电商助手,可以帮助用户查询订单、管理购物车、联系客服等。你可以通过调用后端函数来完成这些操作。请根据用户意图,决定是否需要调用函数,以及调用哪个函数。' }, { role: 'user', content: '帮我查一下订单号为ORD-2024-1001的物流状态。' } ]; try { const completion = await openai.chat.completions.create({ model: 'gpt-4o', // 或 gpt-4-turbo, gpt-3.5-turbo messages, tools, tool_choice: 'auto', // 让模型自行决定是否调用工具 }); const responseMessage = completion.choices[0].message; // 6. 检查模型是否决定调用函数 const toolCalls = responseMessage.tool_calls; if (toolCalls && toolCalls.length > 0) { console.log('模型决定调用函数:', toolCalls[0].function.name); console.log('调用参数:', toolCalls[0].function.arguments); // 根据函数名找到对应的函数定义 const calledFunc = availableFunctions.find(f => f.name === toolCalls[0].function.name); if (!calledFunc) { throw new Error(`未找到函数定义: ${toolCalls[0].function.name}`); } // 7. 执行函数调用(即发送HTTP请求到你的后端) // 这里假设我们有一个执行函数 const executionResult = await executeHttpFunction(calledFunc, JSON.parse(toolCalls[0].function.arguments)); console.log('函数执行结果:', executionResult); // 8. 将结果返回给LLM,让LLM组织语言回复用户 messages.push(responseMessage); // 加入模型的回复(包含工具调用) messages.push({ role: 'tool', tool_call_id: toolCalls[0].id, content: JSON.stringify(executionResult), }); // 进行第二轮对话,让LLM基于结果生成回复 const secondCompletion = await openai.chat.completions.create({ model: 'gpt-4o', messages, }); console.log('助手最终回复:', secondCompletion.choices[0].message.content); } else { // 模型没有调用函数,直接回复 console.log('助手回复:', responseMessage.content); } } catch (error) { console.error('调用OpenAI API出错:', error); } // 执行HTTP请求的辅助函数 async function executeHttpFunction(func: IHttpLlmFunction, args: any) { // 这里需要根据 func.method, func.path, args 来构造HTTP请求 // 例如,如果 func.path 是 `/orders/{orderId}/tracking`, method 是 GET // 我们需要将 args 中的 orderId 替换到路径中,其他查询参数放到URL上 const { host } = { host: 'http://localhost:3000' }; // 你的后端地址 const url = new URL(func.path, host); // 处理路径参数 let finalPath = func.path; for (const [key, value] of Object.entries(args)) { // 简单演示:假设路径参数名与args中的key一致 finalPath = finalPath.replace(`{${key}}`, encodeURIComponent(value as string)); } const options: RequestInit = { method: func.method.toUpperCase(), headers: { 'Content-Type': 'application/json', // 可以在这里添加认证头,例如 Authorization }, }; // 根据方法处理请求体或查询参数 if (['post', 'put', 'patch'].includes(func.method.toLowerCase())) { // 需要过滤掉已用于路径参数的args const bodyArgs = { ...args }; // 这里需要一个更精确的方法来识别哪些是路径参数、查询参数、请求体参数 // 这依赖于从OpenAPI文档中解析出的更详细的信息,IHttpLlmFunction 应该包含这些 // 简化处理:假设所有剩余参数都是请求体 options.body = JSON.stringify(bodyArgs); } else { // GET, DELETE 等,参数放到查询字符串 const searchParams = new URLSearchParams(); for (const [key, value] of Object.entries(args)) { // 同样需要过滤路径参数 searchParams.append(key, String(value)); } url.search = searchParams.toString(); } const response = await fetch(url.toString(), options); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${await response.text()}`); } return await response.json(); }这段代码演示了一个完整的“用户提问 -> LLM选择函数 -> 执行函数 -> LLM基于结果回复”的循环。关键在于第7步的executeHttpFunction。在实际使用@samchon/openapi时,它提供了更强大的HttpLlm.execute方法,能更智能地处理参数映射、请求构造和响应验证。
实操心得:在实际项目中,直接将所有函数(可能上百个)都塞给LLM作为tools参数是不明智的。这会导致上下文窗口被大量占用,增加成本,也可能干扰模型的判断。更好的策略是动态函数路由:根据用户当前对话的上下文或意图,从一个更大的函数池中筛选出最相关的几个(比如5-10个)提供给LLM。这可以基于函数描述的关键词匹配、历史对话分析或更复杂的意图识别模型来实现。
4. 核心进阶:验证反馈与参数分离
让LLM调用函数只是第一步。在真实场景中,LLM生成的参数很可能不符合API的预期格式,比如类型错误、缺少必填字段、枚举值不对等。直接拿着有问题的参数去调用后端,只会得到4xx错误。@samchon/openapi结合typia的强大验证能力,提供了优雅的解决方案。
4.1 验证反馈:让LLM学会“纠错”
LLM,即使是GPT-4,在生成严格符合JSON Schema的参数时也并非百分百可靠。@samchon/openapi的杀手锏之一是验证反馈。当LLM生成的参数验证失败时,它不会直接抛错给用户,而是可以将结构化的错误信息反馈给LLM,让它重新生成。
import { HttpLlm, IValidation } from "@typia/utils"; // 假设我们已经有了 llmApp 和 func const func = llmApp.functions.find(f => f.name === 'get_order_tracking')!; // LLM生成的参数(可能有问题) const llmGeneratedArgs = { orderId: 1001, // 正确应该是字符串 "ORD-2024-1001" // 缺少了必填字段 `carrier`? }; // 使用 typia 进行强类型验证 const validationResult: IValidation<unknown> = func.validate(llmGeneratedArgs); if (!validationResult.success) { console.log('参数验证失败,错误详情:'); validationResult.errors.forEach(err => { console.log(`- 路径: ${err.path}, 信息: ${err.message}`); // 示例输出: // - 路径: orderId, 信息: Expected type is `string`, but value is `1001`. // - 路径: carrier, 信息: Required property is missing. }); // 关键步骤:将错误信息构造为提示,让LLM重试 const errorMessages = validationResult.errors.map(e => `参数"${e.path}": ${e.message}`).join('\n'); const retryPrompt = `你提供的参数有类型错误,请修正:\n${errorMessages}\n请重新生成正确的参数。`; // 将 retryPrompt 作为系统消息或用户消息的一部分,再次调用LLM // const secondAttempt = await openai.chat.completions.create({ // ..., // messages: [...previousMessages, { role: 'user', content: retryPrompt }] // }); } else { // 验证通过,安全执行 const result = await HttpLlm.execute({ connection: { host: 'http://localhost:3000' }, application: llmApp, function: func, input: validationResult.data, // 使用验证后(类型转换后)的数据 }); console.log('执行成功:', result); }这种“验证-反馈-重试”的机制,能显著提高函数调用的成功率。根据官方数据,在电商场景的测试中,首次调用成功率约70%,加入验证反馈后,第二次调用成功率跃升至98%,第三次调用后几乎不再失败。这比让LLM“盲猜”然后由后端返回一个笼统的HTTP错误信息要高效得多。
4.2 参数分离:处理AI不擅长的输入类型
有些参数不适合由LLM生成。最典型的例子是文件上传。你无法让GPT去“生成”一张图片的二进制数据。对于这类参数,我们需要一种机制,将函数的参数分为两部分:一部分由LLM生成(如文本描述、分类标签),另一部分由用户或前端直接提供(如图片文件、音频流)。
@samchon/openapi通过separate配置项支持这种“人机协作”模式。
import { HttpLlm, LlmTypeChecker } from "@typia/utils"; const llmApp = HttpLlm.application({ document: emendedDocument, options: { // 定义一个分离规则:如果参数是字符串类型且 mediaType 以 "image/" 开头,则分离给人类输入 separate: (schema) => LlmTypeChecker.isString(schema) && !!schema.contentMediaType?.startsWith('image/'), }, }); // 假设有一个上传商品图片并创建商品的函数 const createProductFunc = llmApp.functions.find(f => f.path === '/products' && f.method === 'post')!; // 现在,func.separated 对象包含了划分 console.log('AI负责的参数:', createProductFunc.separated.llm); // 可能包含:title, description, price, categoryId 等 console.log('人类负责的参数:', createProductFunc.separated.human); // 可能包含:images (一个文件数组) // 在实际调用时,我们需要合并两部分参数 const llmGeneratedArgs = { title: "限量版运动鞋", description: "2024年新款,轻便透气", price: 899, categoryId: 5 }; const humanProvidedArgs = { images: [/* File 或 Blob 对象数组 */] }; const finalInput = HttpLlm.mergeParameters({ function: createProductFunc, llm: llmGeneratedArgs, human: humanProvidedArgs, }); // 然后使用 finalInput 去执行 const result = await HttpLlm.execute({ connection: { host: 'http://localhost:3000' }, application: llmApp, function: createProductFunc, input: finalInput, });LlmTypeChecker提供了一系列类型判断工具(isString,isNumber,isBoolean,isArray等),让你可以基于参数的JSON Schema定义,精确地控制哪些参数交给AI,哪些留给用户。这个功能在构建需要混合输入(如表单+AI生成内容)的应用时非常有用。
5. 深入生态:MCP集成与生产级框架
@samchon/openapi的价值不仅在于转换HTTP API。它更深层的目标是成为连接AI与各种工具协议的桥梁。Model Context Protocol (MCP) 是另一个重要的生态。
5.1 为什么需要MCP集成?
MCP允许AI模型通过标准协议访问外部工具和资源(如数据库、文件系统、GitHub API)。OpenAI的Agents SDK等工具原生支持通过mcp_servers属性连接MCP服务器。但直接连接有一个问题:上下文爆炸。
一个功能完整的MCP服务器(如GitHub MCP)可能暴露30多个函数。如果全部加载到LLM的上下文中,会占用大量token,增加成本,更严重的是可能导致模型“幻觉”——在面对过多选择时做出错误判断或直接崩溃。
@samchon/openapi处理MCP的思路和处理HTTP API一样:转换、筛选、验证。它使用McpLlm.application()方法,将MCP服务器的工具列表转换为结构化的IMcpLlmApplication。这样,你就可以像管理HTTP函数一样管理MCP函数,并应用同样的验证反馈和动态路由策略。
import { McpLlm, IMcpLlmApplication } from "@typia/utils"; // 假设我们有一个MCP服务器的工具列表 const mcpTools = [...]; // 来自 MCP 服务器发现 const mcpApp: IMcpLlmApplication = McpLlm.application({ tools: mcpTools, }); // 现在,mcpApp.functions 就是一个可以用于LLM Function Calling的列表 // 你可以根据当前对话上下文,只选取相关的几个函数提供给LLM const relevantFunctions = selectRelevantFunctions(mcpApp.functions, userQuery);5.2 生产级框架:Agentica与AutoBE
理解了核心原理,我们来看看如何在实际生产项目中应用。@samchon/openapi本身是底层库,它被集成到更上层的AI应用框架中,其中两个典型代表是Agentica和AutoBE。
Agentica是一个智能体(Agent)框架。它核心的功能之一,就是利用@samchon/openapi将你的后端REST API无缝转化为AI可调用的功能。你只需要提供Swagger文档和认证信息,Agentica就能帮你处理好函数发现、参数验证、请求执行和错误重试等一系列繁琐工作。它让你可以像调用本地TypeScript类方法一样,让AI去调用远程HTTP接口。
AutoBE则展示了另一个维度的应用:AI生成后端代码。传统的AI代码生成是让LLM直接输出源代码字符串,编译成功率低。AutoBE的思路是让AI通过Function Calling去调用“编译器函数”,这些函数接收的是结构化的API设计文档(正是OpenApi.IDocument类型)。AI通过多次调用这些编译器函数,逐步构建出完整的应用AST(抽象语法树),最后由编译器生成100%可编译的代码。在这里,@samchon/openapi提供的类型定义,成为了AI与编译器之间可靠的结构化通信协议。
实操心得:当你计划将AI深度集成到现有系统时,不要只盯着“如何让ChatGPT回答用户问题”。应该从更高维度思考:如何将你系统的“能力”暴露给AI。@samchon/openapi提供了一种标准化、类型安全的方式来做这件事。无论是HTTP API、MCP工具,还是未来可能出现的其他协议,只要它能被描述为结构化的Schema,就可以通过类似的模式被AI理解和调用。这为构建复杂的、由AI协调的自动化工作流打下了坚实的基础。
6. 常见问题与避坑指南
在实际集成过程中,我踩过不少坑,这里总结几个最常见的问题和解决方案。
6.1 问题一:OpenAPI文档不规范导致转换失败
症状:OpenApi.convert()抛出错误,或者转换后的函数列表为空或异常。排查:
- 先用
typia.validate校验文档:这是第一步,也是最重要的一步。它能告诉你文档哪里不符合OpenAPI规范。import typia from "typia"; import { OpenApiV3 } from "@typia/utils"; const validation = typia.validate<OpenApiV3.IDocument>(yourSwaggerDoc); if (!validation.success) { console.error("文档校验失败:", validation.errors); // 根据错误信息修正你的Swagger生成配置(如@nestjs/swagger的装饰器) } - 检查
$ref引用:确保所有$ref指向的#/components/...路径都是存在的,并且没有循环引用。 - 注意
nullable与type数组的混用:在OpenAPI 3.0中,避免同时使用nullable: true和type: ["string", "null"]。选择一种并保持一致。
6.2 问题二:LLM无法正确选择或调用函数
症状:LLM要么不调用函数,要么调用了错误的函数,或者生成的参数完全不对。排查与解决:
- 优化函数名和描述:
HttpLlm.application()默认会生成函数名和描述。但这些自动生成的描述可能不够清晰。你可以通过naming和description选项进行定制,让函数名更语义化,描述更清晰地说明函数的用途、适用场景和参数要求。const llmApp = HttpLlm.application({ document, naming: (path, method, operation) => { // operation 包含原始的OpenAPI操作对象 // 可以优先使用 operation.operationId,如果没有,则生成一个 return operation.operationId || `${method}_${path.replace(/[{}]/g, '').replace(/\//g, '_')}`; }, // 也可以直接提供一个函数来生成描述 // description: (path, method, operation) => operation.summary || operation.description || `API endpoint ${method} ${path}` }); - 实施动态函数路由:不要一次性提供所有函数。根据用户当前对话的意图,从所有函数中筛选出一个子集。例如,当用户问“我的订单”,只提供与订单查询相关的函数;当用户问“推荐商品”,只提供商品搜索和详情函数。这可以大幅提高LLM选择的准确性。
- 提供少量示例(Few-Shot):在系统提示词中,加入一两个函数调用的成功示例,教LLM应该如何调用。例如:“当用户想查询订单时,你应该使用
get_order_by_id函数,并提供orderId参数。”
6.3 问题三:执行HTTP请求时认证失败或参数映射错误
症状:函数调用执行后,后端返回401(未授权)或400(参数错误)。排查与解决:
- 配置连接信息:
HttpLlm.execute需要connection配置,其中应包含基础URL和认证头。
确保你的后端API认证方式(Bearer Token、API Key、OAuth等)在这里被正确设置。await HttpLlm.execute({ connection: { host: 'https://api.your-service.com', headers: { 'Authorization': `Bearer ${process.env.API_TOKEN}`, 'X-API-Key': process.env.API_KEY, }, }, // ... 其他参数 }); - 理解参数映射:
HttpLlm.execute内部会处理OpenAPI中定义的参数位置(path,query,header,cookie,body)。但你需要确保你的OpenAPI文档正确标注了每个参数的位置。例如,一个路径参数{id}必须在文档的parameters中定义为in: "path"。 - 处理文件上传:如果API涉及文件上传(
multipart/form-data),确保在OpenAPI文档中正确定义了contentMediaType(如image/png),并且在使用separate功能时,这部分参数被正确分离并由前端提供。HttpLlm.execute会相应地设置Content-Type请求头。
6.4 问题四:性能与成本考量
症状:AI调用响应慢,或者token消耗过高。优化建议:
- 压缩函数描述:LLM的
tools参数会占用上下文token。检查自动生成的函数description和parametersSchema是否过于冗长。可以尝试在转换后,对Schema进行轻量的简化(例如,移除不必要的examples字段,简化过长的description),但要注意不能破坏其准确性。 - 缓存转换结果:
OpenApi.convert和HttpLlm.application在文档不变的情况下,输出是稳定的。不要在每次请求时都执行转换。应该在服务启动时或文档变更时计算一次,然后将生成的llmApp对象缓存起来。 - 异步验证与执行:
func.validate和HttpLlm.execute可能是IO密集型或计算密集型操作。在服务器端处理时,确保将它们放在异步队列或使用非阻塞方式调用,避免阻塞主事件循环。
最后,一个重要的提醒:虽然@samchon/openapi极大地简化了流程,但AI函数调用并不是银弹。它最适合于结构清晰、职责单一的API。对于逻辑极其复杂、一个调用需要大量条件和上下文的场景,可能需要设计更粗粒度的“组合函数”供AI调用,或者在AI Agent内部设计多步推理和规划。将AI视为一个“会使用工具的高级实习生”,为它设计好上手、不易出错的工具,是整个系统成功的关键。