This commit is contained in:
Michael Kret 2025-09-22 12:10:34 +03:00
parent 47c312f7d3
commit 6eb7b75296
2 changed files with 108 additions and 7 deletions

View File

@ -17,7 +17,7 @@ import type {
Tool,
GenerateContentGenerationConfig,
} from '../../helpers/interfaces';
import { apiRequest } from '../../transport';
import { streamingRequestWithFallback } from '../../transport';
import { modelRLC } from '../descriptions';
const properties: INodeProperties[] = [
@ -319,9 +319,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
: undefined,
};
let response = (await apiRequest.call(this, 'POST', `/v1beta/${model}:generateContent`, {
body,
})) as GenerateContentResponse;
let response = await streamingRequestWithFallback.call(this, model, body);
const maxToolsIterations = this.getNodeParameter('options.maxToolsIterations', i, 15) as number;
const abortSignal = this.getExecutionCancelSignal();
@ -361,9 +359,7 @@ export async function execute(this: IExecuteFunctions, i: number): Promise<INode
});
}
response = (await apiRequest.call(this, 'POST', `/v1beta/${model}:generateContent`, {
body,
})) as GenerateContentResponse;
response = await streamingRequestWithFallback.call(this, model, body);
toolCalls = getToolCalls(response);
currentIteration++;
}

View File

@ -4,6 +4,8 @@ import type {
IHttpRequestMethods,
ILoadOptionsFunctions,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import type { GenerateContentRequest, GenerateContentResponse } from '../helpers/interfaces';
type RequestParameters = {
headers?: IDataObject;
@ -48,3 +50,106 @@ export async function apiRequest(
return await this.helpers.httpRequestWithAuthentication.call(this, 'googlePalmApi', options);
}
async function apiRequestStream(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: IHttpRequestMethods,
endpoint: string,
parameters?: RequestParameters,
): Promise<any> {
const { body, qs, option, headers } = parameters ?? {};
const credentials = await this.getCredentials<GooglePalmApiCredentials>('googlePalmApi');
let url = `https://generativelanguage.googleapis.com${endpoint}`;
if (credentials.host) {
url = `${credentials.host}${endpoint}`;
}
const streamingQs = { ...qs, alt: 'sse' };
const options = {
headers: {
...headers,
Accept: 'text/event-stream',
},
method,
body,
qs: streamingQs,
url,
json: false,
};
if (option && Object.keys(option).length !== 0) {
Object.assign(options, option);
}
const response = await this.helpers.httpRequestWithAuthentication.call(
this,
'googlePalmApi',
options,
);
return parseStreamingResponse(response);
}
function parseStreamingResponse(streamData: string): any {
const lines = streamData.split('\n');
const chunks = [];
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.slice(6);
if (data === '[DONE]') {
break;
}
try {
const parsed = JSON.parse(data);
chunks.push(parsed);
} catch (e) {
continue;
}
}
}
if (chunks.length === 0) {
throw new NodeOperationError(
null as any,
'No valid response chunks received from streaming API',
);
}
const combinedResponse = {
candidates: [
{
content: {
parts: [
{
//TODO: combine other fields
text: chunks
.filter((chunk) => chunk.candidates?.[0]?.content?.parts?.[0]?.text)
.map((chunk) => chunk.candidates[0].content.parts[0].text)
.join(''),
},
],
role: 'model',
},
},
],
};
return combinedResponse;
}
export async function streamingRequestWithFallback(
this: IExecuteFunctions,
model: string,
body: GenerateContentRequest,
) {
try {
return (await apiRequestStream.call(this, 'POST', `/v1beta/${model}:streamGenerateContent`, {
body,
})) as GenerateContentResponse;
} catch (streamError: any) {
return (await apiRequest.call(this, 'POST', `/v1beta/${model}:generateContent`, {
body,
})) as GenerateContentResponse;
}
}