Skip to content

Commit

Permalink
Merge upstream into jm-production
Browse files Browse the repository at this point in the history
  • Loading branch information
jmaddington committed Dec 20, 2024
1 parent 332a947 commit ac507bd
Show file tree
Hide file tree
Showing 125 changed files with 6,869 additions and 975 deletions.
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ BINGAI_TOKEN=user_provided
#============#

GOOGLE_KEY=user_provided

# GOOGLE_REVERSE_PROXY=
# Some reverse proxies do not support the X-goog-api-key header, uncomment to pass the API key in Authorization header instead.
# GOOGLE_AUTH_HEADER=true

# Gemini API (AI Studio)
# GOOGLE_MODELS=gemini-2.0-flash-exp,gemini-exp-1121,gemini-exp-1114,gemini-1.5-flash-latest,gemini-1.0-pro,gemini-1.0-pro-001,gemini-1.0-pro-latest,gemini-1.0-pro-vision-latest,gemini-1.5-pro-latest,gemini-pro,gemini-pro-vision
Expand Down Expand Up @@ -167,6 +170,7 @@ GOOGLE_KEY=user_provided
# GOOGLE_SAFETY_HATE_SPEECH=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_HARASSMENT=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_DANGEROUS_CONTENT=BLOCK_ONLY_HIGH
# GOOGLE_SAFETY_CIVIC_INTEGRITY=BLOCK_ONLY_HIGH

#============#
# OpenAI #
Expand Down
40 changes: 40 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ module.exports = {
'client/dist/**/*',
'client/public/**/*',
'e2e/playwright-report/**/*',
'packages/mcp/types/**/*',
'packages/mcp/dist/**/*',
'packages/mcp/test_bundle/**/*',
'api/demo/**/*',
'packages/data-provider/types/**/*',
'packages/data-provider/dist/**/*',
'packages/data-provider/test_bundle/**/*',
Expand Down Expand Up @@ -136,6 +140,30 @@ module.exports = {
},
],
},
{
files: './api/demo/**/*.ts',
overrides: [
{
files: '**/*.ts',
parser: '@typescript-eslint/parser',
parserOptions: {
project: './packages/data-provider/tsconfig.json',
},
},
],
},
{
files: './packages/mcp/**/*.ts',
overrides: [
{
files: '**/*.ts',
parser: '@typescript-eslint/parser',
parserOptions: {
project: './packages/mcp/tsconfig.json',
},
},
],
},
{
files: './config/translations/**/*.ts',
parser: '@typescript-eslint/parser',
Expand All @@ -149,6 +177,18 @@ module.exports = {
project: './packages/data-provider/tsconfig.spec.json',
},
},
{
files: ['./api/demo/specs/**/*.ts'],
parserOptions: {
project: './packages/data-provider/tsconfig.spec.json',
},
},
{
files: ['./packages/mcp/specs/**/*.ts'],
parserOptions: {
project: './packages/mcp/tsconfig.spec.json',
},
},
],
settings: {
react: {
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/backend-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install Data Provider
- name: Install Data Provider Package
run: npm run build:data-provider

- name: Install MCP Package
run: npm run build:mcp

- name: Create empty auth.json file
run: |
Expand Down
12 changes: 11 additions & 1 deletion Dockerfile.multi
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUN npm config set fetch-retry-maxtimeout 600000 && \
npm config set fetch-retry-mintimeout 15000
COPY package*.json ./
COPY packages/data-provider/package*.json ./packages/data-provider/
COPY packages/mcp/package*.json ./packages/mcp/
COPY client/package*.json ./client/
COPY api/package*.json ./api/
RUN npm ci
Expand All @@ -21,6 +22,14 @@ COPY packages/data-provider ./
RUN npm run build
RUN npm prune --production

# Build mcp package
FROM base AS mcp-build
WORKDIR /app/packages/mcp
COPY packages/mcp ./
COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist
RUN npm run build
RUN npm prune --production

# Client build
FROM base AS client-build
WORKDIR /app/client
Expand All @@ -36,9 +45,10 @@ WORKDIR /app
COPY api ./api
COPY config ./config
COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data-provider/dist
COPY --from=mcp-build /app/packages/mcp/dist ./packages/mcp/dist
COPY --from=client-build /app/client/dist ./client/dist
WORKDIR /app/api
RUN npm prune --production
EXPOSE 3080
ENV HOST=0.0.0.0
CMD ["node", "server/index.js"]
CMD ["node", "server/index.js"]
36 changes: 27 additions & 9 deletions api/app/clients/GoogleClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ const BaseClient = require('./BaseClient');

const loc = process.env.GOOGLE_LOC || 'us-central1';
const publisher = 'google';
const endpointPrefix = `https://${loc}-aiplatform.googleapis.com`;
// const apiEndpoint = loc + '-aiplatform.googleapis.com';
const endpointPrefix = `${loc}-aiplatform.googleapis.com`;
const tokenizersCache = {};

const settings = endpointSettings[EModelEndpoint.google];
Expand All @@ -58,6 +57,10 @@ class GoogleClient extends BaseClient {

this.apiKey = creds[AuthKeys.GOOGLE_API_KEY];

this.reverseProxyUrl = options.reverseProxyUrl;

this.authHeader = options.authHeader;

if (options.skipSetOptions) {
return;
}
Expand All @@ -66,7 +69,7 @@ class GoogleClient extends BaseClient {

/* Google specific methods */
constructUrl() {
return `${endpointPrefix}/v1/projects/${this.project_id}/locations/${loc}/publishers/${publisher}/models/${this.modelOptions.model}:serverStreamingPredict`;
return `https://${endpointPrefix}/v1/projects/${this.project_id}/locations/${loc}/publishers/${publisher}/models/${this.modelOptions.model}:serverStreamingPredict`;
}

async getClient() {
Expand Down Expand Up @@ -595,7 +598,21 @@ class GoogleClient extends BaseClient {
createLLM(clientOptions) {
const model = clientOptions.modelName ?? clientOptions.model;
clientOptions.location = loc;
clientOptions.endpoint = `${loc}-aiplatform.googleapis.com`;
clientOptions.endpoint = endpointPrefix;

let requestOptions = null;
if (this.reverseProxyUrl) {
requestOptions = {
baseUrl: this.reverseProxyUrl,
};

if (this.authHeader) {
requestOptions.customHeaders = {
Authorization: `Bearer ${this.apiKey}`,
};
}
}

if (this.project_id && this.isTextModel) {
logger.debug('Creating Google VertexAI client');
return new GoogleVertexAI(clientOptions);
Expand All @@ -607,10 +624,7 @@ class GoogleClient extends BaseClient {
return new ChatVertexAI(clientOptions);
} else if (!EXCLUDED_GENAI_MODELS.test(model)) {
logger.debug('Creating GenAI client');
return new GenAI(this.apiKey).getGenerativeModel({
...clientOptions,
model,
});
return new GenAI(this.apiKey).getGenerativeModel({ ...clientOptions, model }, requestOptions);
}

logger.debug('Creating Chat Google Generative AI client');
Expand Down Expand Up @@ -683,7 +697,7 @@ class GoogleClient extends BaseClient {
promptPrefix = `${promptPrefix ?? ''}\n${this.options.artifactsPrompt}`.trim();
}

if (this.options?.promptPrefix?.length) {
if (promptPrefix.length) {
requestOptions.systemInstruction = {
parts: [
{
Expand Down Expand Up @@ -901,6 +915,10 @@ class GoogleClient extends BaseClient {
threshold:
process.env.GOOGLE_SAFETY_DANGEROUS_CONTENT || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
{
category: 'HARM_CATEGORY_CIVIC_INTEGRITY',
threshold: process.env.GOOGLE_SAFETY_CIVIC_INTEGRITY || 'HARM_BLOCK_THRESHOLD_UNSPECIFIED',
},
];
}

Expand Down
11 changes: 8 additions & 3 deletions api/app/clients/OpenAIClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class OpenAIClient extends BaseClient {
this.checkVisionRequest(this.options.attachments);
}

this.isO1Model = /\bo1\b/i.test(this.modelOptions.model);
const o1Pattern = /\bo1\b/i;
this.isO1Model = o1Pattern.test(this.modelOptions.model);

const { OPENROUTER_API_KEY, OPENAI_FORCE_PROMPT } = process.env ?? {};
if (OPENROUTER_API_KEY && !this.azure) {
Expand Down Expand Up @@ -147,7 +148,7 @@ class OpenAIClient extends BaseClient {
const { model } = this.modelOptions;

this.isChatCompletion =
/\bo1\b/i.test(model) || model.includes('gpt') || this.useOpenRouter || !!reverseProxy;
o1Pattern.test(model) || model.includes('gpt') || this.useOpenRouter || !!reverseProxy;
this.isChatGptModel = this.isChatCompletion;
if (
model.includes('text-davinci') ||
Expand Down Expand Up @@ -1325,7 +1326,11 @@ ${convo}
/** @type {(value: void | PromiseLike<void>) => void} */
let streamResolve;

if (this.isO1Model === true && this.azure && modelOptions.stream) {
if (
this.isO1Model === true &&
(this.azure || /o1(?!-(?:mini|preview)).*$/.test(modelOptions.model)) &&
modelOptions.stream
) {
delete modelOptions.stream;
delete modelOptions.stop;
}
Expand Down
46 changes: 28 additions & 18 deletions api/app/clients/tools/util/fileSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ const primeFiles = async (options) => {
* @param {Object} options
* @param {ServerRequest} options.req
* @param {Array<{ file_id: string; filename: string }>} options.files
* @param {string} [options.entity_id]
* @returns
*/
const createFileSearchTool = async ({ req, files }) => {
const createFileSearchTool = async ({ req, files, entity_id }) => {
return tool(
async ({ query }) => {
if (files.length === 0) {
Expand All @@ -62,27 +63,36 @@ const createFileSearchTool = async ({ req, files }) => {
if (!jwtToken) {
return 'There was an error authenticating the file search request.';
}

/**
*
* @param {import('librechat-data-provider').TFile} file
* @returns {{ file_id: string, query: string, k: number, entity_id?: string }}
*/
const createQueryBody = (file) => {
const body = {
file_id: file.file_id,
query,
k: 5,
};
if (!entity_id) {
return body;
}
body.entity_id = entity_id;
logger.debug(`[${Tools.file_search}] RAG API /query body`, body);
return body;
};

const queryPromises = files.map((file) =>
axios
.post(
`${process.env.RAG_API_URL}/query`,
{
file_id: file.file_id,
query,
k: 5,
},
{
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
.post(`${process.env.RAG_API_URL}/query`, createQueryBody(file), {
headers: {
Authorization: `Bearer ${jwtToken}`,
'Content-Type': 'application/json',
},
)
})
.catch((error) => {
logger.error(
`Error encountered in \`file_search\` while querying file_id ${file._id}:`,
error,
);
logger.error('Error encountered in `file_search` while querying file:', error);
return null;
}),
);
Expand Down
37 changes: 33 additions & 4 deletions api/app/clients/tools/util/handleTools.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { Tools } = require('librechat-data-provider');
const { Tools, Constants } = require('librechat-data-provider');
const { SerpAPI } = require('@langchain/community/tools/serpapi');
const { Calculator } = require('@langchain/community/tools/calculator');
const { createCodeExecutionTool, EnvVar } = require('@librechat/agents');
Expand All @@ -21,9 +21,12 @@ const {
} = require('../');
const { primeFiles: primeCodeFiles } = require('~/server/services/Files/Code/process');
const { createFileSearchTool, primeFiles: primeSearchFiles } = require('./fileSearch');
const { createMCPTool } = require('~/server/services/MCP');
const { loadSpecs } = require('./loadSpecs');
const { logger } = require('~/config');

const mcpToolPattern = new RegExp(`^.+${Constants.mcp_delimiter}.+$`);

/**
* Validates the availability and authentication of tools for a user based on environment variables or user-specific plugin authentication values.
* Tools without required authentication or with valid authentication are considered valid.
Expand Down Expand Up @@ -146,10 +149,25 @@ const loadToolWithAuth = (userId, authFields, ToolConstructor, options = {}) =>
};
};

/**
*
* @param {object} object
* @param {string} object.user
* @param {Agent} [object.agent]
* @param {string} [object.model]
* @param {EModelEndpoint} [object.endpoint]
* @param {LoadToolOptions} [object.options]
* @param {boolean} [object.useSpecs]
* @param {Array<string>} object.tools
* @param {boolean} [object.functions]
* @param {boolean} [object.returnMap]
* @returns {Promise<{ loadedTools: Tool[], toolContextMap: Object<string, any> } | Record<string,Tool>>}
*/
const loadTools = async ({
user,
agent,
model,
isAgent,
endpoint,
useSpecs,
tools = [],
options = {},
Expand Down Expand Up @@ -190,8 +208,9 @@ const loadTools = async ({
toolConstructors.dalle = DALLE3;
}

/** @type {ImageGenOptions} */
const imageGenOptions = {
isAgent,
isAgent: !!agent,
req: options.req,
fileStrategy: options.fileStrategy,
processFileURL: options.processFileURL,
Expand Down Expand Up @@ -221,6 +240,7 @@ const loadTools = async ({

const toolContextMap = {};
const remainingTools = [];
const appTools = options.req?.app?.locals?.availableTools ?? {};

for (const tool of tools) {
if (tool === Tools.execute_code) {
Expand Down Expand Up @@ -249,9 +269,18 @@ const loadTools = async ({
if (toolContext) {
toolContextMap[tool] = toolContext;
}
return createFileSearchTool({ req: options.req, files });
return createFileSearchTool({ req: options.req, files, entity_id: agent?.id });
};
continue;
} else if (tool && appTools[tool] && mcpToolPattern.test(tool)) {
requestedTools[tool] = async () =>
createMCPTool({
req: options.req,
toolKey: tool,
model: agent?.model ?? model,
provider: agent?.provider ?? endpoint,
});
continue;
}

if (customConstructors[tool]) {
Expand Down
Loading

0 comments on commit ac507bd

Please sign in to comment.