UNPKG

langchain

Version:
202 lines (200 loc) 8.19 kB
import { isBaseChatModel, isConfigurableModel } from "./model.js"; import { StructuredOutputParsingError } from "./errors.js"; import { isInteropZodObject, isInteropZodSchema } from "@langchain/core/utils/types"; import { Validator, toJsonSchema } from "@langchain/core/utils/json_schema"; //#region src/agents/responses.ts /** * This is a global counter for generating unique names for tools. */ let bindingIdentifier = 0; /** * Information for tracking structured output tool metadata. * This contains all necessary information to handle structured responses generated * via tool calls, including the original schema, its type classification, and the * corresponding tool implementation used by the tools strategy. */ var ToolStrategy = class ToolStrategy { constructor(schema, tool, options) { this.schema = schema; this.tool = tool; this.options = options; } get name() { return this.tool.function.name; } static fromSchema(schema, outputOptions) { /** * It is required for tools to have a name so we can map the tool call to the correct tool * when parsing the response. */ function getFunctionName(name) { return name ?? `extract-${++bindingIdentifier}`; } if (isInteropZodSchema(schema)) { const asJsonSchema$1 = toJsonSchema(schema); const tool$1 = { type: "function", function: { name: getFunctionName(), strict: false, description: asJsonSchema$1.description ?? "Tool for extracting structured output from the model's response.", parameters: asJsonSchema$1 } }; return new ToolStrategy(asJsonSchema$1, tool$1, outputOptions); } let functionDefinition; if (typeof schema.name === "string" && typeof schema.parameters === "object" && schema.parameters != null) functionDefinition = schema; else functionDefinition = { name: getFunctionName(schema.title), description: schema.description ?? "", parameters: schema.schema || schema }; const asJsonSchema = toJsonSchema(schema); const tool = { type: "function", function: functionDefinition }; return new ToolStrategy(asJsonSchema, tool, outputOptions); } /** * Parse tool arguments according to the schema. * * @throws {StructuredOutputParsingError} if the response is not valid * @param toolArgs - The arguments from the tool call * @returns The parsed response according to the schema type */ parse(toolArgs) { const validator = new Validator(this.schema); const result = validator.validate(toolArgs); if (!result.valid) throw new StructuredOutputParsingError(this.name, result.errors.map((e) => e.error)); return toolArgs; } }; var ProviderStrategy = class ProviderStrategy { _schemaType; constructor(schema) { this.schema = schema; } static fromSchema(schema) { const asJsonSchema = toJsonSchema(schema); return new ProviderStrategy(asJsonSchema); } /** * Parse tool arguments according to the schema. If the response is not valid, return undefined. * * @param toolArgs - The arguments from the tool call * @returns The parsed response according to the schema type */ parse(response) { /** * return if the response doesn't contain valid content */ if (typeof response.content !== "string" || response.content === "") return; try { const content = JSON.parse(response.content); const validator = new Validator(this.schema); const result = validator.validate(content); if (!result.valid) return; return content; } catch {} } }; /** * Handle user input for `responseFormat` parameter of `CreateAgentParams`. * This function defines the default behavior for the `responseFormat` parameter, which is: * * - if value is a Zod schema, default to structured output via tool calling * - if value is a JSON schema, default to structured output via tool calling * - if value is a custom response format, return it as is * - if value is an array, ensure all array elements are instance of `ToolStrategy` * @param responseFormat - The response format to transform, provided by the user * @param options - The response format options for tool strategy * @param model - The model to check if it supports JSON schema output * @returns */ function transformResponseFormat(responseFormat, options, model) { if (!responseFormat) return []; if (typeof responseFormat === "object" && responseFormat !== null && "__responseFormatUndefined" in responseFormat) return []; /** * If users provide an array, it should only contain raw schemas (Zod or JSON schema), * not ToolStrategy or ProviderStrategy instances. */ if (Array.isArray(responseFormat)) { /** * if every entry is a ToolStrategy or ProviderStrategy instance, return the array as is */ if (responseFormat.every((item) => item instanceof ToolStrategy || item instanceof ProviderStrategy)) return responseFormat; /** * Check if all items are Zod schemas */ if (responseFormat.every((item) => isInteropZodObject(item))) return responseFormat.map((item) => ToolStrategy.fromSchema(item, options)); /** * Check if all items are plain objects (JSON schema) */ if (responseFormat.every((item) => typeof item === "object" && item !== null && !isInteropZodObject(item))) return responseFormat.map((item) => ToolStrategy.fromSchema(item, options)); throw new Error("Invalid response format: list contains mixed types.\nAll items must be either InteropZodObject or plain JSON schema objects."); } if (responseFormat instanceof ToolStrategy || responseFormat instanceof ProviderStrategy) return [responseFormat]; const useProviderStrategy = hasSupportForJsonSchemaOutput(model); /** * `responseFormat` is a Zod schema */ if (isInteropZodObject(responseFormat)) return useProviderStrategy ? [ProviderStrategy.fromSchema(responseFormat)] : [ToolStrategy.fromSchema(responseFormat, options)]; /** * Handle plain object (JSON schema) */ if (typeof responseFormat === "object" && responseFormat !== null && "properties" in responseFormat) return useProviderStrategy ? [ProviderStrategy.fromSchema(responseFormat)] : [ToolStrategy.fromSchema(responseFormat, options)]; throw new Error(`Invalid response format: ${String(responseFormat)}`); } /** * Define how to transform the response format from a tool call. * * @param responseFormat - The response format to transform * @param options - The options to use for the transformation * @param options.handleError - Whether to handle errors from the tool call * @returns The transformed response format */ function toolStrategy(responseFormat, options) { return transformResponseFormat(responseFormat, options); } function providerStrategy(responseFormat) { return ProviderStrategy.fromSchema(responseFormat); } const CHAT_MODELS_THAT_SUPPORT_JSON_SCHEMA_OUTPUT = ["ChatOpenAI", "ChatXAI"]; const MODEL_NAMES_THAT_SUPPORT_JSON_SCHEMA_OUTPUT = [ "grok", "gpt-5", "gpt-4.1", "gpt-4o", "gpt-oss", "o3-pro", "o3-mini" ]; /** * Identifies the models that support JSON schema output * @param model - The model to check * @returns True if the model supports JSON schema output, false otherwise */ function hasSupportForJsonSchemaOutput(model) { if (!model) return false; if (typeof model === "string") { const modelName = model.split(":").pop(); return MODEL_NAMES_THAT_SUPPORT_JSON_SCHEMA_OUTPUT.some((modelNameSnippet) => modelName.includes(modelNameSnippet)); } if (isConfigurableModel(model)) { const configurableModel = model; return hasSupportForJsonSchemaOutput(configurableModel._defaultConfig.model); } if (!isBaseChatModel(model)) return false; const chatModelClass = model.getName(); /** * for testing purposes only */ if (chatModelClass === "FakeToolCallingChatModel") return true; if (CHAT_MODELS_THAT_SUPPORT_JSON_SCHEMA_OUTPUT.includes(chatModelClass) && ("model" in model && MODEL_NAMES_THAT_SUPPORT_JSON_SCHEMA_OUTPUT.some((modelNameSnippet) => typeof model.model === "string" && model.model.includes(modelNameSnippet)) || chatModelClass === "FakeToolCallingModel" && "structuredResponse" in model)) return true; return false; } //#endregion export { ProviderStrategy, ToolStrategy, hasSupportForJsonSchemaOutput, providerStrategy, toolStrategy, transformResponseFormat }; //# sourceMappingURL=responses.js.map