mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
fix(Google Vertex Chat Model Node): Preserve Gemini thought signature on single tool calls (#34797)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: yehorkardash <yehor.kardash@n8n.io>
This commit is contained in:
parent
18964aaeb5
commit
c6e5ecd861
|
|
@ -223,7 +223,8 @@
|
|||
"vuedraggable@4.1.0": "patches/vuedraggable@4.1.0.patch",
|
||||
"assert@2.1.0": "patches/assert@2.1.0.patch",
|
||||
"lodash@4.18.1": "patches/lodash@4.18.1.patch",
|
||||
"@langchain/openai@1.4.4": "patches/@langchain__openai@1.4.4.patch"
|
||||
"@langchain/openai@1.4.4": "patches/@langchain__openai@1.4.4.patch",
|
||||
"@langchain/google-common@2.1.24": "patches/@langchain__google-common@2.1.24.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@ import {
|
|||
type SupplyData,
|
||||
type ILoadOptionsFunctions,
|
||||
type JsonObject,
|
||||
type NodeError,
|
||||
NodeOperationError,
|
||||
validateNodeParameters,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { makeErrorFromStatus } from './error-handling';
|
||||
import { extractGoogleErrorMessage, makeErrorFromStatus } from './error-handling';
|
||||
import { getAdditionalOptions } from '../gemini-common/additional-options';
|
||||
import {
|
||||
getVertexEndpoint,
|
||||
|
|
@ -27,6 +28,14 @@ import {
|
|||
vertexLocationField,
|
||||
} from '../gemini-common/vertex-location';
|
||||
|
||||
function errorDescriptionMapper(error: NodeError) {
|
||||
if (error.description?.includes('properties: should be non-empty for OBJECT type')) {
|
||||
return 'Google Vertex requires at least one <a href="https://docs.n8n.io/advanced-ai/examples/using-the-fromai-function/" target="_blank">dynamic parameter</a> when using tools';
|
||||
}
|
||||
|
||||
return error.description ?? 'Unknown error';
|
||||
}
|
||||
|
||||
export class LmChatGoogleVertex implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Google Vertex Chat Model',
|
||||
|
|
@ -198,11 +207,12 @@ export class LmChatGoogleVertex implements INodeType {
|
|||
temperature: options.temperature,
|
||||
maxOutputTokens: options.maxOutputTokens,
|
||||
safetySettings,
|
||||
callbacks: [new N8nLlmTracing(this)],
|
||||
callbacks: [new N8nLlmTracing(this, { errorDescriptionMapper })],
|
||||
// Handle ChatVertexAI invocation errors to provide better error messages
|
||||
onFailedAttempt: makeN8nLlmFailedAttemptHandler(this, (error: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
const customError = makeErrorFromStatus(Number(error?.response?.status), {
|
||||
const status = Number(error?.response?.status);
|
||||
const customError = makeErrorFromStatus(status, {
|
||||
modelName,
|
||||
});
|
||||
|
||||
|
|
@ -210,6 +220,18 @@ export class LmChatGoogleVertex implements INodeType {
|
|||
throw new NodeOperationError(this.getNode(), error as JsonObject, customError);
|
||||
}
|
||||
|
||||
if (status === 400) {
|
||||
// Surface Google's error detail; a bare rethrow would hide it behind a
|
||||
// generic "Bad request" wrapper
|
||||
throw new NodeOperationError(this.getNode(), error as JsonObject, {
|
||||
message: 'Bad request - please check your parameters',
|
||||
description:
|
||||
extractGoogleErrorMessage(error as { message?: string }) ??
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
(typeof error?.message === 'string' ? (error.message as string) : undefined),
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,3 +23,40 @@ export function makeErrorFromStatus(statusCode: number, context?: ErrorContext):
|
|||
|
||||
return errorMessages[statusCode];
|
||||
}
|
||||
|
||||
interface GoogleErrorLike {
|
||||
message?: string;
|
||||
response?: { status?: number; data?: unknown };
|
||||
}
|
||||
|
||||
function readGoogleErrorMessage(data: unknown): string | undefined {
|
||||
// Google error bodies look like `{ "error": { "message": "..." } }`, sometimes array-wrapped
|
||||
const entry: unknown = Array.isArray(data) ? data[0] : data;
|
||||
if (entry && typeof entry === 'object' && 'error' in entry) {
|
||||
const innerError = (entry as { error?: { message?: unknown } }).error;
|
||||
if (innerError && typeof innerError.message === 'string') return innerError.message;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the human-readable Google API error message from a failed request, so it can be
|
||||
* surfaced to the user instead of a generic status-code wrapper.
|
||||
*/
|
||||
export function extractGoogleErrorMessage(error: GoogleErrorLike): string | undefined {
|
||||
const fromData = readGoogleErrorMessage(error.response?.data);
|
||||
if (fromData) return fromData;
|
||||
|
||||
// @langchain/google-common embeds the raw body in the error message:
|
||||
// "Google request failed with status code <n>: <body>"
|
||||
const match = error.message?.match(/status code \d+: ([\s\S]+)/);
|
||||
if (match) {
|
||||
try {
|
||||
return readGoogleErrorMessage(JSON.parse(match[1])) ?? match[1];
|
||||
} catch {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,4 +201,81 @@ describe('LmChatGoogleVertex - Thinking Budget', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supplyData - failed attempt error handling', () => {
|
||||
const getCustomErrorHandler = async () => {
|
||||
const mockContext = setupMockContext();
|
||||
|
||||
mockContext.getNodeParameter = vi.fn().mockImplementation((paramName: string) => {
|
||||
if (paramName === 'modelName') return 'gemini-3.1-flash-lite';
|
||||
if (paramName === 'projectId') return 'test-project';
|
||||
if (paramName === 'options') return {};
|
||||
if (paramName === 'options.safetySettings.values') return null;
|
||||
return undefined;
|
||||
});
|
||||
|
||||
await lmChatGoogleVertex.supplyData.call(mockContext, 0);
|
||||
|
||||
return mockedMakeN8nLlmFailedAttemptHandler.mock.calls[0][1]!;
|
||||
};
|
||||
|
||||
it('should map 403 to a friendly unauthorized error', async () => {
|
||||
const handler = await getCustomErrorHandler();
|
||||
|
||||
expect(() => handler({ response: { status: 403 } })).toThrowError(
|
||||
'Unauthorized for this project',
|
||||
);
|
||||
});
|
||||
|
||||
it('should surface the Google error detail on 400', async () => {
|
||||
const handler = await getCustomErrorHandler();
|
||||
const error = {
|
||||
message: 'Google request failed with status code 400',
|
||||
response: {
|
||||
status: 400,
|
||||
data: { error: { message: 'Function call is missing a thought_signature' } },
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
handler(error);
|
||||
expect.unreachable('handler should throw');
|
||||
} catch (e) {
|
||||
expect(e).toMatchObject({
|
||||
message: 'Bad request - please check your parameters',
|
||||
description: 'Function call is missing a thought_signature',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should fall back to the raw error message on 400 when no Google detail is found', async () => {
|
||||
const handler = await getCustomErrorHandler();
|
||||
const error = {
|
||||
message: 'Request had invalid authentication scopes',
|
||||
response: { status: 400 },
|
||||
};
|
||||
|
||||
try {
|
||||
handler(error);
|
||||
expect.unreachable('handler should throw');
|
||||
} catch (e) {
|
||||
expect(e).toMatchObject({
|
||||
message: 'Bad request - please check your parameters',
|
||||
description: 'Request had invalid authentication scopes',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('should rethrow the original error for unmapped statuses', async () => {
|
||||
const handler = await getCustomErrorHandler();
|
||||
const error = { message: 'boom', response: { status: 500 } };
|
||||
|
||||
try {
|
||||
handler(error);
|
||||
expect.unreachable('handler should throw');
|
||||
} catch (e) {
|
||||
expect(e).toBe(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import { extractGoogleErrorMessage, makeErrorFromStatus } from '../error-handling';
|
||||
|
||||
describe('makeErrorFromStatus', () => {
|
||||
it('should map 403 to an unauthorized error', () => {
|
||||
expect(makeErrorFromStatus(403)).toEqual({
|
||||
message: 'Unauthorized for this project',
|
||||
description:
|
||||
'Check your Google Cloud project ID, that your credential has access to that project and that billing is enabled',
|
||||
});
|
||||
});
|
||||
|
||||
it('should map 404 with the model name', () => {
|
||||
expect(makeErrorFromStatus(404, { modelName: 'gemini-3.1-flash-lite' })).toEqual({
|
||||
message: "No model found called 'gemini-3.1-flash-lite'",
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined for unmapped statuses', () => {
|
||||
expect(makeErrorFromStatus(400)).toBeUndefined();
|
||||
expect(makeErrorFromStatus(500)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractGoogleErrorMessage', () => {
|
||||
it('should read the message from response data', () => {
|
||||
const error = {
|
||||
message: 'Google request failed with status code 400',
|
||||
response: {
|
||||
status: 400,
|
||||
data: { error: { code: 400, message: 'Function call is missing a thought_signature' } },
|
||||
},
|
||||
};
|
||||
expect(extractGoogleErrorMessage(error)).toBe('Function call is missing a thought_signature');
|
||||
});
|
||||
|
||||
it('should read the message from array-wrapped response data', () => {
|
||||
const error = {
|
||||
response: {
|
||||
status: 400,
|
||||
data: [{ error: { message: 'properties: should be non-empty for OBJECT type' } }],
|
||||
},
|
||||
};
|
||||
expect(extractGoogleErrorMessage(error)).toBe(
|
||||
'properties: should be non-empty for OBJECT type',
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse the body embedded in the error message', () => {
|
||||
const error = {
|
||||
message:
|
||||
'Google request failed with status code 400: {"error":{"message":"Function call is missing a thought_signature","status":"INVALID_ARGUMENT"}}',
|
||||
};
|
||||
expect(extractGoogleErrorMessage(error)).toBe('Function call is missing a thought_signature');
|
||||
});
|
||||
|
||||
it('should fall back to the raw embedded body when it is not JSON', () => {
|
||||
const error = {
|
||||
message: 'Google request failed with status code 400: something went wrong',
|
||||
};
|
||||
expect(extractGoogleErrorMessage(error)).toBe('something went wrong');
|
||||
});
|
||||
|
||||
it('should return undefined when no detail is available', () => {
|
||||
expect(extractGoogleErrorMessage({ message: 'Bad request' })).toBeUndefined();
|
||||
expect(extractGoogleErrorMessage({})).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -191,21 +191,30 @@ interface ProcessedToolResponse {
|
|||
* The structure matches what @langchain/google-common expects:
|
||||
* - `__gemini_function_call_thought_signatures__`: maps the first tool call ID to the signature
|
||||
* - `tool_calls`: array of tool call descriptors
|
||||
* - `signatures`: array aligned to parts [textPart, functionCall_1, ...], with the signature
|
||||
* only on the first function call (per Google's docs)
|
||||
* - `signatures`: array positionally aligned to the Gemini request parts, with the signature
|
||||
* only on the first function call (per Google's docs). @langchain/google-common applies it
|
||||
* only when `signatures.length` equals the part count, so the array must account for whether
|
||||
* the message content produces a leading text part.
|
||||
*
|
||||
* @param toolCalls - Tool calls to include
|
||||
* @param thoughtSignature - The Gemini thought signature
|
||||
* @param hasLeadingTextPart - Whether the message content serializes to a text part before the
|
||||
* function-call parts
|
||||
* @returns additional_kwargs object for AIMessage
|
||||
*/
|
||||
function buildGeminiAdditionalKwargs(
|
||||
toolCalls: Array<{ id: string; name: string; args: IDataObject }>,
|
||||
thoughtSignature: string,
|
||||
hasLeadingTextPart: boolean,
|
||||
): Record<string, unknown> {
|
||||
const signatures: string[] = ['', thoughtSignature];
|
||||
for (let i = 2; i <= toolCalls.length; i++) {
|
||||
signatures.push('');
|
||||
}
|
||||
const signatures = [
|
||||
// Reserve the first position for the leading text part, which has no signature
|
||||
...(hasLeadingTextPart ? [''] : []),
|
||||
// Gemini attaches the thought signature only to the first function call
|
||||
thoughtSignature,
|
||||
// Reserve positions for the remaining unsigned function calls
|
||||
...toolCalls.slice(1).map(() => ''),
|
||||
];
|
||||
|
||||
return {
|
||||
__gemini_function_call_thought_signatures__: {
|
||||
|
|
@ -242,6 +251,8 @@ function buildIndividualAIMessage(
|
|||
? buildGeminiAdditionalKwargs(
|
||||
[{ id: toolId, name: toolName, args: toolInput }],
|
||||
providerMetadata.thoughtSignature,
|
||||
// Empty content produces no text part, so the function call is the first part
|
||||
content !== null && content.length > 0,
|
||||
)
|
||||
: {};
|
||||
// DeepSeek's thinking mode requires reasoning_content to be echoed back on the
|
||||
|
|
@ -289,7 +300,8 @@ function buildSharedGeminiAIMessage(
|
|||
return new AIMessage({
|
||||
content: `Calling tools: ${toolNames}`,
|
||||
tool_calls: allToolCalls,
|
||||
additional_kwargs: buildGeminiAdditionalKwargs(allToolCalls, thoughtSignature),
|
||||
// String content serializes to a text part preceding the function-call parts
|
||||
additional_kwargs: buildGeminiAdditionalKwargs(allToolCalls, thoughtSignature, true),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1519,6 +1519,10 @@ describe('buildSteps', () => {
|
|||
expect(message.additional_kwargs.__gemini_function_call_thought_signatures__).toEqual({
|
||||
call_123: 'gemini_thought_sig_abc123',
|
||||
});
|
||||
// Content is empty, so the function call is the only Gemini request part; the
|
||||
// signatures array must align with the parts or google-common drops it entirely
|
||||
expect(message.content).toEqual([]);
|
||||
expect(message.additional_kwargs.signatures).toEqual(['gemini_thought_sig_abc123']);
|
||||
});
|
||||
|
||||
it('should group parallel tool calls into shared AIMessage with Gemini signature', () => {
|
||||
|
|
|
|||
28
patches/@langchain__google-common@2.1.24.patch
Normal file
28
patches/@langchain__google-common@2.1.24.patch
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
diff --git a/dist/auth.cjs b/dist/auth.cjs
|
||||
index 37cb150e17c88285b79d62883715b33dab80ac6a..6c73f2a6f4d6e05c97bf1dd8c6334bb6ef4e7638 100644
|
||||
--- a/dist/auth.cjs
|
||||
+++ b/dist/auth.cjs
|
||||
@@ -54,6 +54,9 @@ var GoogleAbstractedFetchClient = class {
|
||||
body = JSON.stringify(err.response.data);
|
||||
} catch {}
|
||||
}
|
||||
+ // gaxios (responseType "stream") parses the error body into err.message
|
||||
+ // while leaving response.data unset; fall back so the detail isn't lost
|
||||
+ if (body === undefined && typeof err?.message === "string" && err.message.length > 0) body = err.message;
|
||||
this._throwRequestError(status, body, err?.response ?? { status }, context);
|
||||
}
|
||||
throw fetchError;
|
||||
diff --git a/dist/auth.js b/dist/auth.js
|
||||
index b3f1afa39a53dfb2b4e7020df01e17f54996dacc..111cb0005d210addf190036be9e191597f72da91 100644
|
||||
--- a/dist/auth.js
|
||||
+++ b/dist/auth.js
|
||||
@@ -54,6 +54,9 @@ var GoogleAbstractedFetchClient = class {
|
||||
body = JSON.stringify(err.response.data);
|
||||
} catch {}
|
||||
}
|
||||
+ // gaxios (responseType "stream") parses the error body into err.message
|
||||
+ // while leaving response.data unset; fall back so the detail isn't lost
|
||||
+ if (body === undefined && typeof err?.message === "string" && err.message.length > 0) body = err.message;
|
||||
this._throwRequestError(status, body, err?.response ?? { status }, context);
|
||||
}
|
||||
throw fetchError;
|
||||
|
|
@ -782,6 +782,9 @@ overrides:
|
|||
'@babel/traverse': ^7.23.2
|
||||
|
||||
patchedDependencies:
|
||||
'@langchain/google-common@2.1.24':
|
||||
hash: b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d
|
||||
path: patches/@langchain__google-common@2.1.24.patch
|
||||
'@langchain/openai@1.4.4':
|
||||
hash: 0160a23d307d3bb4aedce23be0d1df4224738bee9184dd4b56bc03eaa37c9a87
|
||||
path: patches/@langchain__openai@1.4.4.patch
|
||||
|
|
@ -3117,7 +3120,7 @@ importers:
|
|||
version: 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))
|
||||
'@langchain/google-common':
|
||||
specifier: 2.1.24
|
||||
version: 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))
|
||||
version: 2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))
|
||||
'@langchain/google-genai':
|
||||
specifier: 2.1.24
|
||||
version: 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))
|
||||
|
|
@ -26599,14 +26602,14 @@ snapshots:
|
|||
- openai
|
||||
- ws
|
||||
|
||||
'@langchain/google-common@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))':
|
||||
'@langchain/google-common@2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))':
|
||||
dependencies:
|
||||
'@langchain/core': 1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(@smithy/signature-v4@5.3.5)(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))
|
||||
uuid: 13.0.1
|
||||
|
||||
'@langchain/google-gauth@2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))':
|
||||
dependencies:
|
||||
'@langchain/google-common': 2.1.24(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))
|
||||
'@langchain/google-common': 2.1.24(patch_hash=b50436a6c7b0a82d54472dff331ba1b42ab8b816995f83295eef2ee4b482a87d)(@langchain/core@1.2.0(@opentelemetry/api@1.9.0)(@opentelemetry/exporter-trace-otlp-proto@0.217.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.7.1(@opentelemetry/api@1.9.0))(openai@6.46.0(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(zod@3.25.67))(ws@8.21.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)))
|
||||
google-auth-library: 10.1.0
|
||||
transitivePeerDependencies:
|
||||
- '@langchain/core'
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user