@samchon/openapi
Version:
OpenAPI definitions and converters for 'typia' and 'nestia'.
159 lines (152 loc) • 5.75 kB
text/typescript
import { OpenApi } from "./OpenApi";
import { LlmSchemaComposer } from "./composers/LlmSchemaComposer";
import { OpenApiV3_1Emender } from "./converters/OpenApiV3_1Emender";
import { ILlmSchema } from "./structures/ILlmSchema";
import { IMcpLlmApplication } from "./structures/IMcpLlmApplication";
import { IMcpLlmFunction } from "./structures/IMcpLlmFunction";
import { IMcpTool } from "./structures/IMcpTool";
import { IOpenApiSchemaError } from "./structures/IOpenApiSchemaError";
import { IResult } from "./structures/IResult";
import { OpenApiTypeChecker } from "./utils/OpenApiTypeChecker";
import { OpenApiValidator } from "./utils/OpenApiValidator";
/**
* Application of LLM function calling from MCP document.
*
* `McpLlm` is a module for composing LLM (Large Language Model) function
* calling application from MCP (Model Context Protocol) document.
*
* The reasons why `@samchon/openapi` recommends to use the function calling
* feature instead of directly using the
* [`mcp_servers`](https://openai.github.io/openai-agents-python/mcp/#using-mcp-servers)
* property of LLM API are:
*
* - Model Specification: {@link ILlmSchema}
* - Validation Feedback: {@link IMcpLlmFunction.validate}
* - Selector agent for reducing context: [Agentica > Orchestration
* Strategy](https://wrtnlabs.io/agentica/docs/concepts/function-calling/#orchestration-strategy)
*
* @author Jeongho Nam - https://github.com/samchon
*/
export namespace McpLlm {
/**
* Properties for the LLM function calling application composer.
*
* @template Model Target LLM model
*/
export interface IApplicationProps<Model extends ILlmSchema.Model> {
/** Target LLM model. */
model: Model;
/**
* List of tools.
*
* A list of tools defined in the MCP (Model Context Protocol) document.
*
* It is better to validate the tools using the
* [`typia.assert<T>()`](https://typia.io/docs/validate/assert) function for
* type safety.
*/
tools: Array<IMcpTool>;
/** Options for the LLM function calling schema conversion. */
options?: Partial<IMcpLlmApplication.IOptions<Model>>;
}
/**
* Convert MCP document to LLM function calling application.
*
* Converts MCP (Model Context Protocol) to LLM (Large Language Model)
* function calling application.
*
* The reasons why `@samchon/openapi` recommends using the function calling
* feature instead of directly using the
* [`mcp_servers`](https://openai.github.io/openai-agents-python/mcp/#using-mcp-servers)
* property of LLM API are:
*
* - **Model Specification**: {@link ILlmSchema}
* - **Validation Feedback**: {@link IMcpLlmFunction.validate}
* - **Selector agent for reducing context**: [Agentica > Orchestration
* Strategy](https://wrtnlabs.io/agentica/docs/concepts/function-calling/#orchestration-strategy)
*
* @param props Properties for composition
* @returns LLM function calling application
*/
export const application = <Model extends ILlmSchema.Model>(
props: IApplicationProps<Model>,
): IMcpLlmApplication<Model> => {
const options: IMcpLlmApplication.IOptions<Model> = {
...Object.fromEntries(
Object.entries(LlmSchemaComposer.defaultConfig(props.model)).map(
([key, value]) =>
[key, (props.options as any)?.[key] ?? value] as const,
),
),
maxLength: props.options?.maxLength ?? 64,
} as IMcpLlmApplication.IOptions<Model>;
const functions: IMcpLlmFunction<Model>[] = [];
const errors: IMcpLlmApplication.IError[] = [];
props.tools.forEach((tool, i) => {
// CONVERT TO EMENDED OPENAPI V3.1 SPECIFICATION
const components: OpenApi.IComponents =
OpenApiV3_1Emender.convertComponents({
schemas: tool.inputSchema.$defs,
});
const schema: OpenApi.IJsonSchema = OpenApiV3_1Emender.convertSchema({
schemas: tool.inputSchema.$defs,
})(tool.inputSchema);
if (components.schemas) {
const visited: Set<string> = new Set<string>();
OpenApiTypeChecker.visit({
closure: (schema: any) => {
if (typeof schema.$ref === "string")
visited.add(schema.$ref.split("/").pop()!);
},
components,
schema,
});
components.schemas = Object.fromEntries(
Object.entries(components.schemas).filter(([key]) =>
visited.has(key),
),
);
}
// CONVERT TO LLM PARAMETERS
const parameters: IResult<
ILlmSchema.IParameters<Model>,
IOpenApiSchemaError
> = LlmSchemaComposer.parameters(props.model)({
config: options as any,
components,
schema: schema as
| OpenApi.IJsonSchema.IObject
| OpenApi.IJsonSchema.IReference,
accessor: `$input.tools[${i}].inputSchema`,
}) as IResult<ILlmSchema.IParameters<Model>, IOpenApiSchemaError>;
if (parameters.success)
functions.push({
name: tool.name,
parameters: parameters.value,
description: tool.description,
validate: OpenApiValidator.create({
components,
schema,
required: true,
equals: options.equals,
}),
});
else
errors.push({
name: tool.name,
parameters: tool.inputSchema,
description: tool.description,
messages: parameters.error.reasons.map((r) => {
const accessor: string = `$input.tools[${i}].inputSchema`;
return `${accessor}: ${r.message}`;
}),
});
});
return {
model: props.model,
functions,
options,
errors,
};
};
}