🧬 chore: Align LibreChat With Agents LangChain Upgrade (#12922)

* 🔧 chore: Update dependencies in package-lock.json and package.json

- Bump version of @librechat/agents to 3.1.75-dev.0 in multiple package.json files.
- Upgrade various AWS SDK and Smithy dependencies to their latest versions in package-lock.json for improved stability and performance.

* 🔧 chore: Update AWS SDK and Smithy dependencies in package-lock.json

- Bump version of @aws-sdk/client-bedrock-runtime to 3.1041.0 and update related dependencies for improved performance and stability.
- Upgrade various AWS SDK and Smithy packages to their latest versions, ensuring compatibility and enhanced functionality.

* chore: Align LibreChat with agents LangChain upgrade

- Route LangChain imports through @librechat/agents facade exports
- Update @librechat/agents to 3.1.75-dev.1 and remove direct LangChain deps
- Normalize nullable agent model params and API key override typing
- Update Google thinking config typing for newer LangChain packages
- Refresh targeted audit-related dependency overrides

* chore: Add Jest types for API specs

* test: Fix LangChain upgrade CI specs

* test: Exercise agents env facade

* fix: Clean up TS preview diagnostics

* fix: Address Codex review feedback
This commit is contained in:
Danny Avila 2026-05-04 01:46:01 +09:00 committed by GitHub
parent 4e45e8e17c
commit 1b79e0b785
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1395 additions and 1141 deletions

View file

@ -1,6 +1,10 @@
const { ToolMessage } = require('@langchain/core/messages');
const { ContentTypes } = require('librechat-data-provider');
const { HumanMessage, AIMessage, SystemMessage } = require('@langchain/core/messages');
const {
AIMessage,
ToolMessage,
HumanMessage,
SystemMessage,
} = require('@librechat/agents/langchain/messages');
const { formatAgentMessages } = require('./formatMessages');
describe('formatAgentMessages', () => {

View file

@ -1,6 +1,10 @@
const { ToolMessage } = require('@langchain/core/messages');
const { EModelEndpoint, ContentTypes } = require('librechat-data-provider');
const { HumanMessage, AIMessage, SystemMessage } = require('@langchain/core/messages');
const {
AIMessage,
ToolMessage,
HumanMessage,
SystemMessage,
} = require('@librechat/agents/langchain/messages');
/**
* Formats a message to OpenAI Vision API payload format.
@ -191,7 +195,7 @@ const formatAgentMessages = (payload) => {
let args = _args;
try {
args = JSON.parse(_args);
} catch (e) {
} catch (_e) {
if (typeof _args === 'string') {
args = { input: _args };
}

View file

@ -1,5 +1,5 @@
const { Constants } = require('librechat-data-provider');
const { HumanMessage, AIMessage, SystemMessage } = require('@langchain/core/messages');
const { HumanMessage, AIMessage, SystemMessage } = require('@librechat/agents/langchain/messages');
const { formatMessage, formatLangChainMessages, formatFromLangChain } = require('./formatMessages');
describe('formatMessage', () => {

View file

@ -1,4 +1,4 @@
const { PromptTemplate } = require('@langchain/core/prompts');
const { PromptTemplate } = require('@librechat/agents/langchain/prompts');
/*
* Without `{summary}` and `{new_lines}`, token count is 98
* We are counting this towards the max context tokens for summaries, +3 for the assistant label (101)

View file

@ -1,5 +1,5 @@
const { Tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { Tool } = require('@librechat/agents/langchain/tools');
const { SearchClient, AzureKeyCredential } = require('@azure/search-documents');
const azureAISearchJsonSchema = {

View file

@ -2,8 +2,8 @@ const path = require('path');
const OpenAI = require('openai');
const { v4: uuidv4 } = require('uuid');
const { ProxyAgent, fetch } = require('undici');
const { Tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { Tool } = require('@librechat/agents/langchain/tools');
const { getImageBasename, extractBaseURL } = require('@librechat/api');
const { FileContext, ContentTypes } = require('librechat-data-provider');

View file

@ -1,9 +1,9 @@
const axios = require('axios');
const fetch = require('node-fetch');
const { v4: uuidv4 } = require('uuid');
const { Tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { Tool } = require('@librechat/agents/langchain/tools');
const { FileContext, ContentTypes } = require('librechat-data-provider');
const fluxApiJsonSchema = {

View file

@ -3,8 +3,8 @@ const sharp = require('sharp');
const { v4 } = require('uuid');
const { ProxyAgent } = require('undici');
const { GoogleGenAI } = require('@google/genai');
const { tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { tool } = require('@librechat/agents/langchain/tools');
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
const {
geminiToolkit,

View file

@ -1,5 +1,5 @@
const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
const { Tool } = require('@librechat/agents/langchain/tools');
const { getEnvironmentVariable } = require('@librechat/agents/langchain/utils/env');
const googleSearchJsonSchema = {
type: 'object',

View file

@ -3,9 +3,9 @@ const { v4 } = require('uuid');
const OpenAI = require('openai');
const FormData = require('form-data');
const { ProxyAgent } = require('undici');
const { tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { tool } = require('@librechat/agents/langchain/tools');
const { ContentTypes, EImageOutputType } = require('librechat-data-provider');
const { logAxiosError, oaiToolkit, extractBaseURL } = require('@librechat/api');
const { getStrategyFunctions } = require('~/server/services/Files/strategies');

View file

@ -1,6 +1,6 @@
const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
const fetch = require('node-fetch');
const { Tool } = require('@librechat/agents/langchain/tools');
const { getEnvironmentVariable } = require('@librechat/agents/langchain/utils/env');
const openWeatherJsonSchema = {
type: 'object',

View file

@ -4,8 +4,8 @@ const path = require('path');
const axios = require('axios');
const sharp = require('sharp');
const { v4: uuidv4 } = require('uuid');
const { Tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { Tool } = require('@librechat/agents/langchain/tools');
const { FileContext, ContentTypes } = require('librechat-data-provider');
const { getBasePath } = require('@librechat/api');
const paths = require('~/config/paths');

View file

@ -1,6 +1,6 @@
const { z } = require('zod');
const { ProxyAgent, fetch } = require('undici');
const { tool } = require('@langchain/core/tools');
const { tool } = require('@librechat/agents/langchain/tools');
const { getApiKey } = require('./credentials');
function createTavilySearchTool(fields = {}) {

View file

@ -1,6 +1,6 @@
const { ProxyAgent, fetch } = require('undici');
const { Tool } = require('@langchain/core/tools');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
const { Tool } = require('@librechat/agents/langchain/tools');
const { getEnvironmentVariable } = require('@librechat/agents/langchain/utils/env');
const tavilySearchJsonSchema = {
type: 'object',

View file

@ -1,6 +1,6 @@
const { Tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
const { Tool } = require('@librechat/agents/langchain/tools');
const { getEnvironmentVariable } = require('@librechat/agents/langchain/utils/env');
const traversaalSearchJsonSchema = {
type: 'object',

View file

@ -1,7 +1,7 @@
/* eslint-disable no-useless-escape */
const axios = require('axios');
const { Tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { Tool } = require('@librechat/agents/langchain/tools');
const wolframJsonSchema = {
type: 'object',

View file

@ -1,4 +1,4 @@
const { getEnvironmentVariable } = require('@langchain/core/utils/env');
const { getEnvironmentVariable } = require('@librechat/agents/langchain/utils/env');
function getApiKey(envVar, override) {
const key = getEnvironmentVariable(envVar);

View file

@ -1,7 +1,6 @@
const GoogleSearch = require('../GoogleSearch');
jest.mock('node-fetch');
jest.mock('@langchain/core/utils/env');
describe('GoogleSearch', () => {
let originalEnv;

View file

@ -2,7 +2,6 @@ const { fetch, ProxyAgent } = require('undici');
const TavilySearchResults = require('../TavilySearchResults');
jest.mock('undici');
jest.mock('@langchain/core/utils/env');
describe('TavilySearchResults', () => {
let originalEnv;

View file

@ -12,8 +12,8 @@ const axios = require('axios');
const OpenAI = require('openai');
const undici = require('undici');
const fetch = require('node-fetch');
const { ToolMessage } = require('@langchain/core/messages');
const { ContentTypes } = require('librechat-data-provider');
const { ToolMessage } = require('@librechat/agents/langchain/messages');
const StableDiffusionAPI = require('../StableDiffusion');
const FluxAPI = require('../FluxAPI');
const DALLE3 = require('../DALLE3');

View file

@ -1,6 +1,6 @@
const axios = require('axios');
const { tool } = require('@langchain/core/tools');
const { logger } = require('@librechat/data-schemas');
const { tool } = require('@librechat/agents/langchain/tools');
const { generateShortLivedToken } = require('@librechat/api');
const { Tools, EToolResources } = require('librechat-data-provider');
const { filterFilesByAgentAccess } = require('~/server/services/Files/permissions');

View file

@ -106,8 +106,8 @@ const validateTools = async (user, tools = []) => {
}
};
/** @typedef {typeof import('@langchain/core/tools').Tool} ToolConstructor */
/** @typedef {import('@langchain/core/tools').Tool} Tool */
/** @typedef {typeof import('@librechat/agents/langchain/tools').Tool} ToolConstructor */
/** @typedef {import('@librechat/agents/langchain/tools').Tool} Tool */
/**
* Initializes a tool with authentication values for the given user, supporting alternate authentication fields.

View file

@ -38,13 +38,12 @@
"@aws-sdk/client-bedrock-runtime": "^3.1013.0",
"@aws-sdk/client-s3": "^3.980.0",
"@aws-sdk/s3-request-presigner": "^3.758.0",
"@azure/identity": "^4.7.0",
"@azure/identity": "^4.13.1",
"@azure/search-documents": "^12.0.0",
"@azure/storage-blob": "^12.30.0",
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
"@librechat/agents": "^3.1.75",
"@librechat/agents": "^3.1.75-dev.1",
"@librechat/api": "*",
"@librechat/data-schemas": "*",
"@microsoft/microsoft-graph-client": "^3.0.7",

View file

@ -1,6 +1,6 @@
require('events').EventEmitter.defaultMaxListeners = 100;
const { logger } = require('@librechat/data-schemas');
const { getBufferString, HumanMessage } = require('@langchain/core/messages');
const { getBufferString, HumanMessage } = require('@librechat/agents/langchain/messages');
const {
createRun,
isEnabled,

View file

@ -1250,7 +1250,7 @@ describe('AgentClient - titleConvo', () => {
'# MCP Server Instructions\n\nTest MCP instructions here',
);
const { DynamicStructuredTool } = require('@langchain/core/tools');
const { DynamicStructuredTool } = require('@librechat/agents/langchain/tools');
// Create mock MCP tools with the delimiter pattern
const mockMCPTool1 = new DynamicStructuredTool({
@ -1482,7 +1482,7 @@ describe('AgentClient - titleConvo', () => {
});
it('should filter out image URLs from message content', async () => {
const { HumanMessage, AIMessage } = require('@langchain/core/messages');
const { HumanMessage, AIMessage } = require('@librechat/agents/langchain/messages');
const messages = [
new HumanMessage({
content: [
@ -1538,7 +1538,7 @@ describe('AgentClient - titleConvo', () => {
});
it('should handle messages with only text content', async () => {
const { HumanMessage, AIMessage } = require('@langchain/core/messages');
const { HumanMessage, AIMessage } = require('@librechat/agents/langchain/messages');
const messages = [
new HumanMessage('Hello, how are you?'),
new AIMessage('I am doing well, thank you!'),
@ -1556,7 +1556,7 @@ describe('AgentClient - titleConvo', () => {
});
it('should handle mixed content types correctly', async () => {
const { HumanMessage } = require('@langchain/core/messages');
const { HumanMessage } = require('@librechat/agents/langchain/messages');
const { ContentTypes } = require('librechat-data-provider');
const messages = [
@ -1593,7 +1593,7 @@ describe('AgentClient - titleConvo', () => {
});
it('should preserve original messages without mutation', async () => {
const { HumanMessage } = require('@langchain/core/messages');
const { HumanMessage } = require('@librechat/agents/langchain/messages');
const originalContent = [
{
type: 'text',
@ -1622,7 +1622,7 @@ describe('AgentClient - titleConvo', () => {
});
it('should handle message window size correctly', async () => {
const { HumanMessage, AIMessage } = require('@langchain/core/messages');
const { HumanMessage, AIMessage } = require('@librechat/agents/langchain/messages');
const messages = [
new HumanMessage('Message 1'),
new AIMessage('Response 1'),
@ -1646,7 +1646,7 @@ describe('AgentClient - titleConvo', () => {
});
it('should return early if processMemory is not set', async () => {
const { HumanMessage } = require('@langchain/core/messages');
const { HumanMessage } = require('@librechat/agents/langchain/messages');
client.processMemory = null;
const result = await client.runMemory([new HumanMessage('Test')]);

View file

@ -1,7 +1,7 @@
const jwt = require('jsonwebtoken');
const { nanoid } = require('nanoid');
const { tool } = require('@langchain/core/tools');
const { GraphEvents, sleep } = require('@librechat/agents');
const { tool } = require('@librechat/agents/langchain/tools');
const { logger, encryptV2, decryptV2 } = require('@librechat/data-schemas');
const {
sendEvent,

View file

@ -1,4 +1,4 @@
const { tool } = require('@langchain/core/tools');
const { tool } = require('@librechat/agents/langchain/tools');
const { logger, getTenantId } = require('@librechat/data-schemas');
const {
Providers,

View file

@ -1,12 +1,12 @@
const { logger } = require('@librechat/data-schemas');
const { tool: toolFn, DynamicStructuredTool } = require('@langchain/core/tools');
const { tool: toolFn, DynamicStructuredTool } = require('@librechat/agents/langchain/tools');
const {
sleep,
StepTypes,
GraphEvents,
createToolSearch,
Constants: AgentConstants,
createBashExecutionTool,
Constants: AgentConstants,
createProgrammaticToolCallingTool,
} = require('@librechat/agents');
const {
@ -17,10 +17,10 @@ const {
GenerationJobManager,
isActionDomainAllowed,
buildWebSearchContext,
buildWebSearchDynamicContext,
buildImageToolContext,
buildToolClassification,
buildOAuthToolCallName,
buildToolClassification,
buildWebSearchDynamicContext,
} = require('@librechat/api');
const {
Time,

View file

@ -1,9 +1,9 @@
const fs = require('fs');
const path = require('path');
const { Tool } = require('@langchain/core/tools');
const { Calculator } = require('@librechat/agents');
const { logger } = require('@librechat/data-schemas');
const { zodToJsonSchema } = require('zod-to-json-schema');
const { Tool } = require('@librechat/agents/langchain/tools');
const { Tools, ImageVisionTool } = require('librechat-data-provider');
const { getToolkitKey, oaiToolkit, geminiToolkit } = require('@librechat/api');
const { toolkits } = require('~/app/clients/tools/manifest');

View file

@ -162,25 +162,25 @@
/**
* @exports BaseMessage
* @typedef {import('@langchain/core/messages').BaseMessage} BaseMessage
* @typedef {import('@librechat/agents/langchain/messages').BaseMessage} BaseMessage
* @memberof typedefs
*/
/**
* @exports UsageMetadata
* @typedef {import('@langchain/core/messages').UsageMetadata} UsageMetadata
* @typedef {import('@librechat/agents/langchain/messages').UsageMetadata} UsageMetadata
* @memberof typedefs
*/
/**
* @exports LangChainToolCall
* @typedef {import('@langchain/core/messages/tool').ToolCall} LangChainToolCall
* @typedef {import('@librechat/agents/langchain/messages/tool').ToolCall} LangChainToolCall
* @memberof typedefs
*/
/**
* @exports GraphRunnableConfig
* @typedef {import('@langchain/core/runnables').RunnableConfig<{
* @typedef {import('@librechat/agents/langchain/runnables').RunnableConfig<{
* req: ServerRequest;
* thread_id: string;
* run_id: string;

2214
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -139,20 +139,15 @@
"typescript-eslint": "^8.24.0"
},
"overrides": {
"@anthropic-ai/sdk": "0.73.0",
"@xmldom/xmldom": "^0.8.13",
"@librechat/agents": {
"@langchain/anthropic": {
"@anthropic-ai/sdk": "0.73.0",
"fast-xml-parser": "5.6.0"
},
"@anthropic-ai/sdk": "0.73.0",
"fast-xml-parser": "5.6.0"
},
"elliptic": "^6.6.1",
"fast-xml-parser": "5.6.0",
"form-data": "^4.0.4",
"langsmith": "^0.6.0",
"postcss": "^8.5.13",
"tslib": "^2.8.1",
"@anthropic-ai/sdk": "^0.92.0",
"fast-xml-parser": "5.7.2",
"serialize-javascript": "7.0.5",
"mdast-util-gfm-autolink-literal": "2.0.0",
"remark-gfm": {
"mdast-util-gfm-autolink-literal": "2.0.0"
@ -169,7 +164,6 @@
"katex": "^0.16.21"
}
},
"langsmith": "0.4.12",
"eslint": {
"ajv": "6.14.0"
},

View file

@ -89,13 +89,12 @@
"@anthropic-ai/vertex-sdk": "^0.14.3",
"@aws-sdk/client-bedrock-runtime": "^3.1013.0",
"@aws-sdk/client-s3": "^3.980.0",
"@azure/identity": "^4.7.0",
"@azure/identity": "^4.13.1",
"@azure/search-documents": "^12.0.0",
"@azure/storage-blob": "^12.30.0",
"@google/genai": "^1.19.0",
"@keyv/redis": "^4.3.3",
"@langchain/core": "^0.3.80",
"@librechat/agents": "^3.1.75",
"@librechat/agents": "^3.1.75-dev.1",
"@librechat/data-schemas": "*",
"@modelcontextprotocol/sdk": "^1.29.0",
"@smithy/node-http-handler": "^4.4.5",

View file

@ -33,7 +33,7 @@ jest.mock('@librechat/agents', () => ({
}));
import { Types } from 'mongoose';
import { HumanMessage, AIMessage } from '@langchain/core/messages';
import { HumanMessage, AIMessage } from '@librechat/agents/langchain/messages';
import {
scopeSkillIds,
resolveSkillActive,

View file

@ -1,5 +1,5 @@
import { PromptTemplate } from '@langchain/core/prompts';
import { BaseMessage, getBufferString } from '@langchain/core/messages';
import { PromptTemplate } from '@librechat/agents/langchain/prompts';
import { BaseMessage, getBufferString } from '@librechat/agents/langchain/messages';
import type { GraphEdge } from '@librechat/agents';
const DEFAULT_PROMPT_TEMPLATE = `Based on the following conversation and analysis from previous agents, please provide your insights:\n\n{convo}\n\nPlease add your specific expertise and perspective to this discussion.`;

View file

@ -7,9 +7,9 @@ import {
estimateOpenAIImageTokens,
estimateAnthropicImageTokens,
} from '@librechat/agents';
import type { BaseMessage } from '@librechat/agents/langchain/messages';
import type { MessageContentComplex } from '@librechat/agents';
import type { Agent, TMessage } from 'librechat-data-provider';
import type { BaseMessage } from '@langchain/core/messages';
import type { ServerRequest } from '~/types';
import Tokenizer from '~/utils/tokenizer';
import { logAxiosError } from '~/utils';

View file

@ -1,6 +1,6 @@
import { z } from 'zod';
import { Constants } from 'librechat-data-provider';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { DynamicStructuredTool } from '@librechat/agents/langchain/tools';
import type { Logger } from 'winston';
import type { MCPManager } from '~/mcp/MCPManager';
import type { AgentWithTools } from './context';

View file

@ -1,5 +1,5 @@
import { Constants } from 'librechat-data-provider';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { DynamicStructuredTool } from '@librechat/agents/langchain/tools';
import type { Agent, TEphemeralAgent } from 'librechat-data-provider';
import type { LCTool } from '@librechat/agents';
import type { Logger } from 'winston';

View file

@ -10,12 +10,12 @@ import type {
ToolExecuteBatchRequest,
} from '@librechat/agents';
import { Types } from 'mongoose';
import type { StructuredToolInterface } from '@langchain/core/tools';
import type { StructuredToolInterface } from '@librechat/agents/langchain/tools';
import type { SkillFileRecord } from './skillFiles';
import type { ServerRequest } from '~/types';
import { buildSkillPrimeMessage } from './skills';
import { cleanCodeToolOutput } from './cleanup';
import { primeSkillFiles } from './skillFiles';
import type { SkillFileRecord } from './skillFiles';
import { buildSkillPrimeMessage } from './skills';
import { runOutsideTracing } from '~/utils';
export interface ToolEndCallbackData {
@ -694,13 +694,13 @@ async function handleReadFileCall(
}
const stream = await strategy.getDownloadStream(req, file.filepath);
const chunks: Buffer[] = [];
const chunks: Uint8Array[] = [];
// Use the larger binary limit as streaming cap; cheaper type-specific
// checks happen after binary detection on the assembled buffer.
const streamLimit = MAX_BINARY_BYTES;
let streamedBytes = 0;
for await (const chunk of stream as AsyncIterable<Buffer>) {
streamedBytes += chunk.length;
for await (const chunk of stream as AsyncIterable<Uint8Array>) {
streamedBytes += chunk.byteLength;
if (streamedBytes > streamLimit) {
// Destroy the stream if possible to free resources
if (

View file

@ -326,7 +326,7 @@ describe('Memory Agent Header Resolution', () => {
model: 'us.anthropic.claude-haiku-4-5-20251001-v1:0',
};
const { HumanMessage } = await import('@langchain/core/messages');
const { HumanMessage } = await import('@librechat/agents/langchain/messages');
const testMessage = new HumanMessage('test chat content');
await processMemory({

View file

@ -1,22 +1,22 @@
/** Memories */
import { z } from 'zod';
import { tool } from '@langchain/core/tools';
import { Tools } from 'librechat-data-provider';
import { logger } from '@librechat/data-schemas';
import { HumanMessage } from '@langchain/core/messages';
import { tool } from '@librechat/agents/langchain/tools';
import { Run, Providers, GraphEvents } from '@librechat/agents';
import { HumanMessage } from '@librechat/agents/langchain/messages';
import type {
OpenAIClientOptions,
StreamEventData,
ToolEndCallback,
ClientOptions,
EventHandler,
ToolEndData,
LLMConfig,
} from '@librechat/agents';
import type { BaseMessage, ToolMessage } from '@librechat/agents/langchain/messages';
import type { DynamicStructuredTool } from '@librechat/agents/langchain/tools';
import type { ObjectId, MemoryMethods, IUser } from '@librechat/data-schemas';
import type { TAttachment, MemoryArtifact } from 'librechat-data-provider';
import type { BaseMessage, ToolMessage } from '@langchain/core/messages';
import type { Response as ServerResponse } from 'express';
import { GenerationJobManager } from '~/stream/GenerationJobManager';
import { resolveHeaders, createSafeUser } from '~/utils';
@ -32,6 +32,8 @@ type ToolEndMetadata = Record<string, unknown> & {
thread_id?: string;
};
type SanitizedMemoryLLMConfig = Omit<Partial<LLMConfig>, 'apiKey'> & { apiKey?: string };
export interface MemoryConfig {
validKeys?: string[];
instructions?: string;
@ -39,6 +41,14 @@ export interface MemoryConfig {
tokenLimit?: number;
}
function normalizeMemoryLLMConfig(llmConfig?: Partial<LLMConfig>): SanitizedMemoryLLMConfig {
const config = { ...(llmConfig ?? {}) } as Record<string, unknown>;
if (typeof config.apiKey !== 'string') {
delete config.apiKey;
}
return config as SanitizedMemoryLLMConfig;
}
export const memoryInstructions =
'The system automatically stores important user information and can update or delete memories based on user requests, enabling dynamic memory management.';
@ -88,7 +98,7 @@ export const createMemoryTool = ({
validKeys?: string[];
tokenLimit?: number;
totalTokens?: number;
}) => {
}): DynamicStructuredTool => {
const remainingTokens = tokenLimit ? tokenLimit - totalTokens : Infinity;
const isOverflowing = tokenLimit ? remainingTokens <= 0 : false;
@ -342,15 +352,15 @@ ${memory ?? 'No existing memories'}`;
disableStreaming: true,
};
const finalLLMConfig: ClientOptions = {
const finalLLMConfig = {
...defaultLLMConfig,
...llmConfig,
...normalizeMemoryLLMConfig(llmConfig),
/**
* Ensure streaming is always disabled for memory processing
*/
streaming: false,
disableStreaming: true,
};
} as LLMConfig;
// Handle GPT-5+ models
if ('model' in finalLLMConfig && /\bgpt-[5-9](?:\.\d+)?\b/i.test(finalLLMConfig.model ?? '')) {

View file

@ -1,4 +1,4 @@
import { ToolMessage, AIMessage, HumanMessage } from '@langchain/core/messages';
import { ToolMessage, AIMessage, HumanMessage } from '@librechat/agents/langchain/messages';
import { extractDiscoveredToolsFromHistory } from './run';
describe('extractDiscoveredToolsFromHistory', () => {

View file

@ -20,13 +20,18 @@ import type {
IState,
LCTool,
} from '@librechat/agents';
import type { Agent, AgentSubagentsConfig, SummarizationConfig } from 'librechat-data-provider';
import type { BaseMessage } from '@langchain/core/messages';
import type {
Agent,
AgentModelParameters,
AgentSubagentsConfig,
SummarizationConfig,
} from 'librechat-data-provider';
import type { BaseMessage } from '@librechat/agents/langchain/messages';
import type { AppConfig, IUser } from '@librechat/data-schemas';
import type * as t from '~/types';
import { getProviderConfig } from '~/endpoints/config/providers';
import { getOpenAIConfig } from '~/endpoints/openai/config';
import { resolveHeaders, createSafeUser } from '~/utils/env';
import { getOpenAIConfig } from '~/endpoints/openai/config';
import { isUserProvided } from '~/utils/common';
/** Expected shape of JSON tool search results */
@ -275,6 +280,31 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
return value != null && typeof value === 'object' && !Array.isArray(value);
}
const nullableAgentModelParameterKeys = [
'temperature',
'maxContextTokens',
'max_context_tokens',
'max_output_tokens',
'top_p',
'frequency_penalty',
'presence_penalty',
] satisfies Array<keyof AgentModelParameters>;
function normalizeAgentModelParameters(
modelParameters: AgentModelParameters | undefined,
): Partial<AgentModelParameters> | undefined {
if (!modelParameters) {
return undefined;
}
const normalized: Partial<AgentModelParameters> = { ...modelParameters };
for (const key of nullableAgentModelParameterKeys) {
if (normalized[key] === null) {
delete normalized[key];
}
}
return normalized;
}
/**
* Merges user-supplied summarization parameters on top of endpoint-resolved
* overrides. User params win for top-level keys; `configuration` is
@ -398,9 +428,11 @@ function resolveSummarizationProvider(
},
rawProvider,
);
const clientOverrides: SummarizationClientOverrides = {
...llmConfig,
};
const { apiKey: resolvedApiKey, ...llmConfigOverrides } = llmConfig;
const clientOverrides: SummarizationClientOverrides = { ...llmConfigOverrides };
if (typeof resolvedApiKey === 'string') {
clientOverrides.apiKey = resolvedApiKey;
}
if (configOptions) {
clientOverrides.configuration = configOptions;
}
@ -718,14 +750,15 @@ export async function createRun({
{ user, requestBody },
);
const llmConfig: t.RunLLMConfig = Object.assign(
const modelParameters = normalizeAgentModelParameters(agent.model_parameters);
const llmConfig = Object.assign(
{
provider,
streaming,
streamUsage,
},
agent.model_parameters,
);
modelParameters,
) as t.RunLLMConfig;
const joinInstructionMap = (map?: Record<string, unknown>) =>
Object.values(map ?? {})

View file

@ -1,9 +1,9 @@
import { logger } from '@librechat/data-schemas';
import { HumanMessage } from '@langchain/core/messages';
import { isEphemeralAgentId } from 'librechat-data-provider';
import { HumanMessage } from '@librechat/agents/langchain/messages';
import { formatSkillCatalog, SkillToolDefinition } from '@librechat/agents';
import type { LCToolRegistry, LCTool, InjectedMessage } from '@librechat/agents';
import type { BaseMessage } from '@langchain/core/messages';
import type { BaseMessage } from '@librechat/agents/langchain/messages';
import type { Agent } from 'librechat-data-provider';
import type { Types } from 'mongoose';
import type { InitializeAgentDbMethods } from './initialize';

View file

@ -138,7 +138,7 @@ function getLLMConfig(
let requestOptions: AnthropicClientOptions & { stream?: boolean } = {
model: mergedOptions.model,
stream: mergedOptions.stream,
temperature: mergedOptions.temperature,
temperature: mergedOptions.temperature ?? undefined,
stopSequences: mergedOptions.stop,
maxTokens:
mergedOptions.maxOutputTokens || anthropicSettings.maxOutputTokens.reset(mergedOptions.model),

View file

@ -384,7 +384,7 @@ describe('getGoogleConfig', () => {
expect(result.llmConfig).toHaveProperty('thinkingConfig');
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: ThinkingLevel.high,
thinkingLevel: 'HIGH',
});
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).not.toHaveProperty(
'thinkingBudget',
@ -406,7 +406,26 @@ describe('getGoogleConfig', () => {
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: ThinkingLevel.medium,
thinkingLevel: 'MEDIUM',
});
});
it('should preserve minimal thinkingLevel for Gemini 3 Flash models', () => {
const credentials = {
[AuthKeys.GOOGLE_API_KEY]: 'test-api-key',
};
const result = getGoogleConfig(credentials, {
modelOptions: {
model: 'gemini-3-flash-preview',
thinking: true,
thinkingLevel: ThinkingLevel.minimal,
},
});
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: 'MINIMAL',
});
});
@ -466,7 +485,7 @@ describe('getGoogleConfig', () => {
expect(result.provider).toBe(Providers.VERTEXAI);
expect((result.llmConfig as Record<string, unknown>).thinkingConfig).toMatchObject({
includeThoughts: true,
thinkingLevel: ThinkingLevel.low,
thinkingLevel: 'LOW',
});
expect(result.llmConfig).toHaveProperty('includeThoughts', true);
});

View file

@ -1,10 +1,24 @@
import { Providers } from '@librechat/agents';
import { googleSettings, AuthKeys, removeNullishValues } from 'librechat-data-provider';
import type { GoogleClientOptions, VertexAIClientOptions } from '@librechat/agents';
import type { GoogleAIToolType } from '@langchain/google-common';
import type { GoogleAIToolType } from '@librechat/agents/langchain/google-common';
import type * as t from '~/types';
import { isEnabled } from '~/utils';
type GoogleThinkingLevel = 'THINKING_LEVEL_UNSPECIFIED' | 'MINIMAL' | 'LOW' | 'MEDIUM' | 'HIGH';
type GoogleThinkingConfig = {
includeThoughts: boolean;
thinkingLevel?: GoogleThinkingLevel;
};
const googleThinkingLevels = new Set<GoogleThinkingLevel>([
'THINKING_LEVEL_UNSPECIFIED',
'MINIMAL',
'LOW',
'MEDIUM',
'HIGH',
]);
/** Known Google/Vertex AI parameters that map directly to the client config */
export const knownGoogleParams = new Set([
'model',
@ -70,6 +84,17 @@ function getThresholdMapping(model: string) {
return (value: string) => value;
}
function normalizeGoogleThinkingLevel(value: unknown): GoogleThinkingLevel | undefined {
if (typeof value !== 'string') {
return undefined;
}
const normalized = value.toUpperCase() as GoogleThinkingLevel;
if (!googleThinkingLevels.has(normalized)) {
return undefined;
}
return normalized;
}
export function getSafetySettings(
model?: string,
): Array<{ category: string; threshold: string }> | undefined {
@ -206,22 +231,23 @@ export function getGoogleConfig(
* with `includeThoughts: true`. The `thinkingBudget` param is ignored for Gemini 3+.
*
* For Vertex AI, top-level `includeThoughts` is still required because
* `@langchain/google-common`'s `formatGenerationConfig` reads it separately
* `@librechat/agents/langchain/google-common`'s `formatGenerationConfig` reads it separately
* from `thinkingConfig` they serve different purposes in the request pipeline.
*/
const isGemini3Plus = /gemini-([3-9]|\d{2,})/i.test(modelName);
if (isGemini3Plus && thinking) {
const thinkingConfig: { includeThoughts: boolean; thinkingLevel?: string } = {
const thinkingConfig: GoogleThinkingConfig = {
includeThoughts: true,
};
if (thinkingLevel) {
thinkingConfig.thinkingLevel = thinkingLevel as string;
const normalizedThinkingLevel = normalizeGoogleThinkingLevel(thinkingLevel);
if (normalizedThinkingLevel) {
thinkingConfig.thinkingLevel = normalizedThinkingLevel;
}
if (provider === Providers.GOOGLE) {
(llmConfig as GoogleClientOptions).thinkingConfig = thinkingConfig;
(llmConfig as { thinkingConfig?: GoogleThinkingConfig }).thinkingConfig = thinkingConfig;
} else if (provider === Providers.VERTEXAI) {
(llmConfig as Record<string, unknown>).thinkingConfig = thinkingConfig;
(llmConfig as { thinkingConfig?: GoogleThinkingConfig }).thinkingConfig = thinkingConfig;
(llmConfig as VertexAIClientOptions).includeThoughts = true;
}
} else if (!isGemini3Plus) {

View file

@ -1,7 +1,7 @@
import { EModelEndpoint, removeNullishValues } from 'librechat-data-provider';
import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
import type { BindToolsInput } from '@librechat/agents/langchain/language_models/chat_models';
import type { AzureOpenAIInput } from '@librechat/agents/langchain/openai';
import type { SettingDefinition } from 'librechat-data-provider';
import type { AzureOpenAIInput } from '@langchain/openai';
import type { OpenAI } from 'openai';
import type * as t from '~/types';
import { sanitizeModelName, constructAzureURL } from '~/utils/azure';

View file

@ -1,5 +1,5 @@
import { EModelEndpoint } from 'librechat-data-provider';
import type { GoogleAIToolType } from '@langchain/google-common';
import type { GoogleAIToolType } from '@librechat/agents/langchain/google-common';
import type { ClientOptions } from '@librechat/agents';
import type * as t from '~/types';
import { knownOpenAIParams } from './llm';

View file

@ -1,3 +1,5 @@
/// <reference types="jest" />
/** String.prototype.isWellFormed — ES2024 API, available in Node 20+ but absent from TS 5.3 lib */
interface String {
isWellFormed(): boolean;

View file

@ -1,8 +1,8 @@
import { z } from 'zod';
import { openAISchema } from 'librechat-data-provider';
import type { TConfig } from 'librechat-data-provider';
import type { BindToolsInput } from '@librechat/agents/langchain/language_models/chat_models';
import type { OpenAIClientOptions, Providers } from '@librechat/agents';
import type { BindToolsInput } from '@langchain/core/language_models/chat_models';
import type { TConfig } from 'librechat-data-provider';
import type { AzureOptions } from './azure';
export type OpenAIParameters = z.infer<typeof openAISchema>;

View file

@ -1,6 +1,6 @@
import { ContentTypes, ToolCallTypes } from 'librechat-data-provider';
import type { Agents, PartMetadata, TMessageContentParts } from 'librechat-data-provider';
import type { ToolCall } from '@langchain/core/messages/tool';
import type { ToolCall } from '@librechat/agents/langchain/messages/tool';
import { filterMalformedContentParts } from './content';
describe('filterMalformedContentParts', () => {

View file

@ -1,4 +1,4 @@
import type { BaseMessage } from '@langchain/core/messages';
import type { BaseMessage } from '@librechat/agents/langchain/messages';
/** Signature for a function that counts tokens in a LangChain message. */
export type TokenCounter = (message: BaseMessage) => number;