@langgraph-js/pro
Version:
The Pro SDK for LangGraph - seamlessly integrate your AI agents with frontend interfaces and build complex AI workflows
235 lines (234 loc) • 7.64 kB
JavaScript
import { convertToOpenAITool as formatToOpenAITool, isLangChainTool } from "@langchain/core/utils/function_calling";
import { isInteropZodSchema } from "@langchain/core/utils/types";
import { toJsonSchema } from "@langchain/core/utils/json_schema";
/**
* Formats a tool in either OpenAI format, or LangChain structured tool format
* into an OpenAI tool format. If the tool is already in OpenAI format, return without
* any changes. If it is in LangChain structured tool format, convert it to OpenAI tool format
* using OpenAI's `zodFunction` util, falling back to `convertToOpenAIFunction` if the parameters
* returned from the `zodFunction` util are not defined.
*
* @param {BindToolsInput} tool The tool to convert to an OpenAI tool.
* @param {Object} [fields] Additional fields to add to the OpenAI tool.
* @returns {ToolDefinition} The inputted tool in OpenAI tool format.
*/
export function _convertToOpenAITool(tool, fields) {
let toolDef;
if (isLangChainTool(tool)) {
toolDef = formatToOpenAITool(tool);
}
else {
toolDef = tool;
}
if (fields?.strict !== undefined) {
toolDef.function.strict = fields.strict;
}
return toolDef;
}
function isAnyOfProp(prop) {
return prop.anyOf !== undefined && Array.isArray(prop.anyOf);
}
// When OpenAI use functions in the prompt, they format them as TypeScript definitions rather than OpenAPI JSON schemas.
// This function converts the JSON schemas into TypeScript definitions.
export function formatFunctionDefinitions(functions) {
const lines = ["namespace functions {", ""];
for (const f of functions) {
if (f.description) {
lines.push(`// ${f.description}`);
}
if (Object.keys(f.parameters.properties ?? {}).length > 0) {
lines.push(`type ${f.name} = (_: {`);
lines.push(formatObjectProperties(f.parameters, 0));
lines.push("}) => any;");
}
else {
lines.push(`type ${f.name} = () => any;`);
}
lines.push("");
}
lines.push("} // namespace functions");
return lines.join("\n");
}
// Format just the properties of an object (not including the surrounding braces)
function formatObjectProperties(obj, indent) {
const lines = [];
for (const [name, param] of Object.entries(obj.properties ?? {})) {
if (param.description && indent < 2) {
lines.push(`// ${param.description}`);
}
if (obj.required?.includes(name)) {
lines.push(`${name}: ${formatType(param, indent)},`);
}
else {
lines.push(`${name}?: ${formatType(param, indent)},`);
}
}
return lines.map((line) => " ".repeat(indent) + line).join("\n");
}
// Format a single property type
function formatType(param, indent) {
if (isAnyOfProp(param)) {
return param.anyOf.map((v) => formatType(v, indent)).join(" | ");
}
switch (param.type) {
case "string":
if (param.enum) {
return param.enum.map((v) => `"${v}"`).join(" | ");
}
return "string";
case "number":
if (param.enum) {
return param.enum.map((v) => `${v}`).join(" | ");
}
return "number";
case "integer":
if (param.enum) {
return param.enum.map((v) => `${v}`).join(" | ");
}
return "number";
case "boolean":
return "boolean";
case "null":
return "null";
case "object":
return ["{", formatObjectProperties(param, indent + 2), "}"].join("\n");
case "array":
if (param.items) {
return `${formatType(param.items, indent)}[]`;
}
return "any[]";
default:
return "";
}
}
export function formatToOpenAIAssistantTool(tool) {
return {
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: isInteropZodSchema(tool.schema) ? toJsonSchema(tool.schema) : tool.schema,
},
};
}
export function formatToOpenAIToolChoice(toolChoice) {
if (!toolChoice) {
return undefined;
}
else if (toolChoice === "any" || toolChoice === "required") {
return "required";
}
else if (toolChoice === "auto") {
return "auto";
}
else if (toolChoice === "none") {
return "none";
}
else if (typeof toolChoice === "string") {
return {
type: "function",
function: {
name: toolChoice,
},
};
}
else {
return toolChoice;
}
}
export function isBuiltInTool(tool) {
return "type" in tool && tool.type !== "function";
}
export function isBuiltInToolChoice(tool_choice) {
return tool_choice != null && typeof tool_choice === "object" && "type" in tool_choice && tool_choice.type !== "function";
}
export function isCustomTool(tool) {
return (typeof tool === "object" &&
tool !== null &&
"metadata" in tool &&
typeof tool.metadata === "object" &&
tool.metadata !== null &&
"customTool" in tool.metadata &&
typeof tool.metadata.customTool === "object" &&
tool.metadata.customTool !== null);
}
export function isOpenAICustomTool(tool) {
return "type" in tool && tool.type === "custom" && "custom" in tool && typeof tool.custom === "object" && tool.custom !== null;
}
export function parseCustomToolCall(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rawToolCall) {
if (rawToolCall.type !== "custom_tool_call") {
return undefined;
}
return {
...rawToolCall,
type: "tool_call",
call_id: rawToolCall.id,
id: rawToolCall.call_id,
name: rawToolCall.name,
isCustomTool: true,
args: {
input: rawToolCall.input,
},
};
}
export function isCustomToolCall(toolCall) {
return toolCall.type === "tool_call" && "isCustomTool" in toolCall && toolCall.isCustomTool === true;
}
export function convertCompletionsCustomTool(tool) {
const getFormat = () => {
if (!tool.custom.format) {
return undefined;
}
if (tool.custom.format.type === "grammar") {
return {
type: "grammar",
definition: tool.custom.format.grammar.definition,
syntax: tool.custom.format.grammar.syntax,
};
}
if (tool.custom.format.type === "text") {
return {
type: "text",
};
}
return undefined;
};
return {
type: "custom",
name: tool.custom.name,
description: tool.custom.description,
format: getFormat(),
};
}
export function convertResponsesCustomTool(tool) {
const getFormat = () => {
if (!tool.format) {
return undefined;
}
if (tool.format.type === "grammar") {
return {
type: "grammar",
grammar: {
definition: tool.format.definition,
syntax: tool.format.syntax,
},
};
}
if (tool.format.type === "text") {
return {
type: "text",
};
}
return undefined;
};
return {
type: "custom",
custom: {
name: tool.name,
description: tool.description,
format: getFormat(),
},
};
}