langchain
Version:
Typescript bindings for langchain
202 lines (200 loc) • 8.19 kB
JavaScript
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