我为 OpenAI 和所有其他 LLM 开发了一种比 Anthropico Claude 更便宜的 MCP(模型上下文协议)替代方案。
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
前言
我制作了 MCP(模型上下文协议)的替代方案。
我的替代方案是利用 Swagger/OpenAPI 和 TypeScript 类函数提供的 LLM 函数调用功能,并结合编译器和验证反馈策略进行增强。借助这些策略,您可以将 Anthropic Claude 的 MCP 完全替换为更小的模型gpt-4o-mini(例如,可以替换为本地 LLM)。
以下演示了我的替代方案,即在购物中心搜索和购买商品。其后端服务器包含 289 个 API 函数,我可以通过参数模型中的对话文本调用所有这些 APIgpt-4o-mini函数8b。
- 相关存储库
@samchon/openapiSwagger/OpenAPI 到函数调用模式typia:typia.llm.application<Class, Model>()功能@nestiaNestJS 中的类型安全 OpenAPI 构建器@agentica利用上述技术的智能体人工智能框架(即将推出)
- 购物人工智能聊天机器人演示
@samchon/shopping-backend后端服务器由……构建@nestiaShoppingChatApplication.tsx:使用 React 构建的代理应用程序代码。
import { Agentica } from "@agentica/core";
import { HttpLlm, OpenApi } from "@samchon/openapi";
import typia from "typia";
const agent = new Agentica({
vendor: {
api: new OpenAI({ apiKey: "*****" }),
model: "gpt-4o-mini",
},
controllers: [
HttpLlm.application({
model: "chatgpt",
document: OpenApi.convert(
await fetch(
"https://shopping-be.wrtn.ai/editor/swagger.json",
).then(r => r.json())
)
}),
typia.llm.application<ShoppingCounselor, "chatgpt">(),
typia.llm.application<ShoppingPolicy, "chatgpt">(),
typia.llm.application<ShoppingSearchRag, "chatgpt">(),
],
});
await agent.conversate("I wanna buy MacBook Pro");
脚本:
- 你能做什么?
- 能向我展示一下市场上的销售情况吗?
- 我想查看MacBook的详细信息。选择(银色,16GB,1TB,英文)型号并加入购物车。将购物车添加到订单页面。我将用现金支付,我的地址是~
函数调用
LLM 选择要调用的正确函数,并填充其参数。
- https://platform.openai.com/docs/guides/function-calling
- https://platform.openai.com/docs/guides/structured-outputs
LLM(大型语言模型)函数调用是指 LLM 通过分析与用户的对话上下文来选择合适的函数并填充参数。还有一个类似的概念叫做结构化输出,它指的是 LLM 自动将输出的对话转换为 JSON 等结构化数据格式。
我专注于LLM函数调用功能,并希望用户能够充分利用它。如果能够做到这一点,您就可以完全用它替换Anthropic Claude的MCP(模型上下文协议),使其适用于更小的模型gpt-4o-mini。以下是我希望通过我的解决方案实现的蓝图:
- 用户列出候选函数
- 用户无需设计复杂的代理图或工作流程。
- 我已经在我的购物中心解决方案中实现了这一点。
OpenAPI战略
OpenAPI规范到LLM函数调用方案的转换。
LLM(大型语言模型)函数调用需要基于 JSON 模式的函数模式。然而,不同的 LLM 服务供应商并没有使用相同的 JSON 模式。“OpenAI GPT”和“Anthropologie Claude”使用了不同的 LLM 函数调用 JSON 模式规范,Google Gemini 也与它们不同。
更糟糕的是,Swagger/OpenAPI 文档使用的 JSON 模式规范与 LLM 函数调用模式的规范不同,而且不同版本的 Swagger/OpenAPI 之间的规范差异很大。
为了解决这个问题,我做了如下处理@samchon/openapi。当 Swagger/OpenAPI 文档出现时,它会转换为 OpenAPI v3.1 修订版规范。然后,它会绕过迁移模式,将其转换为服务供应商特定的 LLM 函数调用模式。作为参考,迁移模式是另一种中间件模式,它将 OpenAPI 操作模式转换为类似函数的模式。
此外,在将 Swagger/OpenAPI 文档转换为 LLM 函数调用模式时,嵌入#Validation Feedback@samchon/openapi策略的参数运行时验证器。
验证反馈
import { FunctionCall } from "pseudo";
import { ILlmFunction, IValidation } from "typia";
export const correctFunctionCall = (p: {
call: FunctionCall;
functions: Array<ILlmFunction<"chatgpt">>;
retry: (reason: string, errors?: IValidation.IError[]) => Promise<unknown>;
}): Promise<unknown> => {
// FIND FUNCTION
const func: ILlmFunction<"chatgpt"> | undefined =
p.functions.find((f) => f.name === p.call.name);
if (func === undefined) {
// never happened in my experience
return p.retry(
"Unable to find the matched function name. Try it again.",
);
}
// VALIDATE
const result: IValidation<unknown> = func.validate(p.call.arguments);
if (result.success === false) {
// 1st trial: 30% (gpt-4o-mini in shopping mall chatbot)
// 2nd trial with validation feedback: 99%
// 3nd trial with validation feedback again: never have failed
return p.retry(
"Type errors are detected. Correct it through validation errors",
{
errors: result.errors,
},
);
}
return result.data;
}
LLM 函数调用完美吗?不,绝对不完美。
像 OpenAI 这样的 LLM(大型语言模型)服务供应商在组合函数调用或结构化输出的参数时,经常会出现类型级别的错误。即使目标模式非常简单(例如Array<string>类型),LLM 也常常直接使用类型化的值来填充它string。
根据我的经验,OpenAI gpt-4o-mini(8b参数方面)在向购物中心服务调用函数时,大约 70% 的参数类型错误。为了克服 LLM 函数调用的缺陷,许多 LLM 用户使用大型模型llama-3.2-405b并尝试使用简单的结构化模式。然而,我尝试了另一种方法:验证反馈策略,而不是使用大型模型或简化模式。结果非常成功。
验证反馈策略的关键概念是,先让 LLM 函数调用构造无效的类型化参数,并将详细的类型错误告知 LLM,以便 LLM 在下一轮调用中修正错误的类型化参数。
而且,我已经采用了typia.validate<T>()一些typia.llm.application<Class, Model>()功能来替代 MCP。它们通过在编译级别分析 TypeScript 源代码和类型来构建验证逻辑,因此比任何其他验证器(例如下面的验证器)都更加详细和准确。
通过这种验证反馈策略并结合typia运行时验证器,我实现了最理想的LLM函数调用,可以完全替代MCP(模型上下文协议),从而应用于更小的模型gpt-4o-mini。采用这种策略后,第一次函数调用尝试的成功率从30%提升到第二次函数调用尝试的成功率99%,并且从第三次尝试开始从未失败。
| 成分 | typia |
TypeBox |
ajv |
io-ts |
zod |
C.V. |
|---|---|---|---|---|---|---|
| 便于使用 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 简单对象 | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| 对象(层级式) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| 对象(递归) | ✔ | ❌ | ✔ | ✔ | ✔ | ✔ |
| 对象(联合体,隐式) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 对象(联合体,显式) | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ |
| 对象(附加标签) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| 对象(模板字面量类型) | ✔ | ✔ | ✔ | ❌ | ❌ | ❌ |
| 对象(动态属性) | ✔ | ✔ | ✔ | ❌ | ❌ | ❌ |
| 数组(剩余元组) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 数组(分层) | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| 数组(递归) | ✔ | ✔ | ✔ | ✔ | ✔ | ❌ |
| 数组(递归,并集) | ✔ | ✔ | ❌ | ✔ | ✔ | ❌ |
| 数组(R+U,隐式) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 数组(重复) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 数组(重复,并集) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| 终极联盟类型 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
C.V.方法class-validator- 测试结构:
IShoppingSale - 相关实体关系图:ERD.md#sales
编译器技能
编译器技术能够完美生成模式。
我认为最重要的是,无论是 Anthropic Claude 的 MCP(模型上下文协议)还是我提倡用来取代 MCP 的 LLM(LLM 函数调用)函数调用策略,都要安全地创建 JSON 模式。
顺便一提,我观察了很多其他人工智能开发者,他们都在手动编写 JSON 模式。即使借助一些工具构建器,也常常会造成模式定义重复,这似乎很危险。在传统的开发生态系统中,即使人犯了模式编写错误,其他人也可以通过其所在机构来纠正。然而,人工智能却不会原谅错误。
为了确保模式组合的安全性,我开发了一个基于 TypeScript 编译器的 JSON 模式生成器typia,并且@nestia已经运行了很长时间。如果您编写一条typia.llm.application<MyClass, "chatgpt">()语句,它将被转换为 OpenAI 函数调用模式的集合。这种模式转换是通过在编译级别分析 TypeScript 源代码来完成的typia。
此外,在开发用于生成 OpenAPI 文档的后端服务器时,@nestia也会进行同样的操作typia。它会分析后端服务器的 TypeScript 源代码,以便安全地生成 OpenAPI 文档而不会出现任何错误。
编译器技术能够生成如此完美的模式,我认为我的替代方案比 Anthropic Claude 的 MCP(模型上下文协议)更好。
@Controller("shoppings/customers/sales")
export class ShoppingCustomerSaleController {
/**
* Get a sale with detailed information.
*
* Get a {@link IShoppingSale sale} with detailed information including
* the SKU (Stock Keeping Unit) information represented by the
* {@link IShoppingSaleUnitOption} and {@link IShoppingSaleUnitStock}
* types.
*
* > If you're an A.I. chatbot, and the user wants to buy or compose a
* > {@link IShoppingCartCommodity shopping cart} from a sale, please
* > call this operation at least once to the target sale to get
* > detailed SKU information about the sale.
* >
* > It needs to be run at least once for the next steps. In other
* > words, if you A.I. agent has called this operation to a specific
* > sale, you don't need to call this operation again for the same
* > sale.
* >
* > Additionally, please do not summarize the SKU information.
* > Just show the every options and stocks in the sale with detailed
* > information.
*
* @param id Target sale's {@link IShoppingSale.id}
* @returns Detailed sale information
* @tag Sale
*/
@TypedRoute.Get(":id")
public async at(
@TypedParam("id") id: string & tags.Format<"uuid">,
): Promise<IShoppingSale>;
}
传统的OpenAPI组合方式。
由韩国大型IT公司之一的Toss Corporation编写。
@ExtendWith(RestDocumentationExtension::class, SpringExtension::class) @SpringBootTest class SampleControllerTest { private lateinit var mockMvc: MockMvc @BeforeEach internal fun setUp(context: WebApplicationContext, restDocumentation: RestDocumentationContextProvider) { mockMvc = MockMvcBuilders.webAppContextSetup(context) .apply<DefaultMockMvcBuilder>(MockMvcRestDocumentation.documentationConfiguration(restDocumentation)) .build() } @Test fun getSampleByIdTest() { val sampleId = "aaa" mockMvc.perform( get("/api/v1/samples/{sampleId}", sampleId) ) .andExpect(status().isOk) .andExpect(jsonPath("sampleId", `is`(sampleId))) .andExpect(jsonPath("name", `is`("sample-$sampleId"))) .andDo( MockMvcRestDocumentationWrapper.document( identifier = "sample", resourceDetails = ResourceSnippetParametersBuilder() .tag("Sample") .description("Get a sample by id") .pathParameters( parameterWithName("sampleId") .description("the sample id"), ) .responseFields( fieldWithPath("sampleId") .type(JsonFieldType.STRING) .description("The sample identifier."), fieldWithPath("name") .type(JsonFieldType.STRING) .description("The name of sample."), ), ), ) } }
Agentica,一款取代 MCP 的智能人工智能框架
@nestia/agent为了增强功能并将其拆分为多个扩展包,该功能已被迁移@agentica/*。由于它将由组织层面进行开发,因此您可能会更快地遇到它。
利用上述 OpenAPI、验证反馈和编译器策略,我和我的伙伴们正在开发一个智能体 AI 框架。它可以完全替代 MCP(模型上下文协议),并且模型成本远低于 Anthropic Claude。在我们的案例中,我们尝试使用本地 LLM(局部逻辑层模型)。
新框架的名称是[@agentica此处应填写框架名称],预计将在1到2周后发布。功能已基本完成,只剩下命令行包和文档需要编写。
它@agentica采用了以下多代理编排策略。借助此代理编排策略,用户无需编写复杂的代理图或工作流,只需将 Swagger/OpenAPI 文档或 TypeScript 类类型线性地传递给它即可@agentica。@agentica它将通过函数调用完成所有操作。
当用户发出指令时,@agentica系统会将对话文本传递给selector代理,并让selector代理从上下文中查找(或取消)候选函数。如果代理selector找不到任何可调用的候选函数,且之前也没有选择过任何候选函数,则selector代理将像普通的 ChatGPT 一样运行。
然后@agentica进入循环语句,直到候选函数为空。在循环语句中,caller智能体尝试通过分析用户的对话文本来调用 LLM 函数。如果上下文足以构成候选函数的参数,caller智能体则实际调用目标函数,并decriber解释函数调用结果。否则,上下文不足以构成参数,caller智能体会向用户请求更多信息。
这种 LLM(大型语言模型)函数调用策略将selector、caller和 分开,describer是其关键逻辑@agentica。




