feat: Add AI tool building capabilities (#7336)

Github issue / Community forum post (link here to close automatically):
https://community.n8n.io/t/langchain-memory-chat/23733

---------

Signed-off-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Oleg Ivaniv <me@olegivaniv.com>
Co-authored-by: Val <68596159+valya@users.noreply.github.com>
Co-authored-by: Alex Grozav <alex@grozav.com>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
Co-authored-by: Deborah <deborah@starfallprojects.co.uk>
Co-authored-by: Jesper Bylund <mail@jesperbylund.com>
Co-authored-by: Jon <jonathan.bennetts@gmail.com>
Co-authored-by: Michael Kret <88898367+michael-radency@users.noreply.github.com>
Co-authored-by: Giulio Andreini <andreini@netseven.it>
Co-authored-by: Mason Geloso <Mason.geloso@gmail.com>
Co-authored-by: Mason Geloso <hone@Masons-Mac-mini.local>
Co-authored-by: Mutasem Aldmour <mutasem@n8n.io>
This commit is contained in:
Jan Oberhauser
2023-11-29 12:13:55 +01:00
committed by GitHub
parent dbfd617ace
commit 87def60979
243 changed files with 21526 additions and 321 deletions

View File

@@ -0,0 +1,186 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import {
NodeConnectionType,
type IExecuteFunctions,
type INodeType,
type INodeTypeDescription,
type SupplyData,
} from 'n8n-workflow';
import { logWrapper } from '../../../utils/logWrapper';
import { N8nBinaryLoader } from '../../../utils/N8nBinaryLoader';
import { getConnectionHintNoticeField, metadataFilterField } from '../../../utils/sharedFields';
// Dependencies needed underneath the hood for the loaders. We add them
// here only to track where what dependency is sued
// import 'd3-dsv'; // for csv
import 'mammoth'; // for docx
import '@gxl/epub-parser'; // for epub
import 'pdf-parse'; // for pdf
export class DocumentBinaryInputLoader implements INodeType {
description: INodeTypeDescription = {
// This node is deprecated and will be removed in the future.
// The functionality was merged with the `DocumentJSONInputLoader` to `DocumentDefaultDataLoader`
hidden: true,
displayName: 'Binary Input Loader',
name: 'documentBinaryInputLoader',
icon: 'file:binary.svg',
group: ['transform'],
version: 1,
description: 'Use binary data from a previous step in the workflow',
defaults: {
name: 'Binary Input Loader',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Document Loaders'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.documentbinaryinputloader/',
},
],
},
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputNames: ['Document'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
{
displayName: 'Loader Type',
name: 'loader',
type: 'options',
default: 'jsonLoader',
required: true,
options: [
{
name: 'CSV Loader',
value: 'csvLoader',
description: 'Load CSV files',
},
{
name: 'Docx Loader',
value: 'docxLoader',
description: 'Load Docx documents',
},
{
name: 'EPub Loader',
value: 'epubLoader',
description: 'Load EPub files',
},
{
name: 'JSON Loader',
value: 'jsonLoader',
description: 'Load JSON files',
},
{
name: 'PDF Loader',
value: 'pdfLoader',
description: 'Load PDF documents',
},
{
name: 'Text Loader',
value: 'textLoader',
description: 'Load plain text files',
},
],
},
{
displayName: 'Binary Data Key',
name: 'binaryDataKey',
type: 'string',
default: 'data',
required: true,
description: 'Name of the binary property from which to read the file buffer',
},
// PDF Only Fields
{
displayName: 'Split Pages',
name: 'splitPages',
type: 'boolean',
default: true,
displayOptions: {
show: {
loader: ['pdfLoader'],
},
},
},
// CSV Only Fields
{
displayName: 'Column',
name: 'column',
type: 'string',
default: '',
description: 'Column to extract from CSV',
displayOptions: {
show: {
loader: ['csvLoader'],
},
},
},
{
displayName: 'Separator',
name: 'separator',
type: 'string',
description: 'Separator to use for CSV',
default: ',',
displayOptions: {
show: {
loader: ['csvLoader'],
},
},
},
// JSON Only Fields
{
displayName: 'Pointers',
name: 'pointers',
type: 'string',
default: '',
description: 'Pointers to extract from JSON, e.g. "/text" or "/text, /meta/title"',
displayOptions: {
show: {
loader: ['jsonLoader'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
...metadataFilterField,
displayName: 'Metadata',
description:
'Metadata to add to each document. Could be used for filtering during retrieval',
placeholder: 'Add property',
},
],
},
],
};
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
this.logger.verbose('Supply Data for Binary Input Loader');
const processor = new N8nBinaryLoader(this);
return {
response: logWrapper(processor, this),
};
}
}

View File

@@ -0,0 +1,3 @@
<svg height="1024" width="768" xmlns="http://www.w3.org/2000/svg">
<path d="M0 960V64h576l192 192v704H0zM704 320L512 128H64v768h640V320zM320 512H128V256h192V512zM256 320h-64v128h64V320zM256 768h64v64H128v-64h64V640h-64v-64h128V768zM512 448h64v64H384v-64h64V320h-64v-64h128V448zM576 832H384V576h192V832zM512 640h-64v128h64V640z" fill="#7D7D87" />
</svg>

After

Width:  |  Height:  |  Size: 355 B

View File

@@ -0,0 +1,270 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import {
NodeConnectionType,
type IExecuteFunctions,
type INodeType,
type INodeTypeDescription,
type SupplyData,
} from 'n8n-workflow';
import { logWrapper } from '../../../utils/logWrapper';
import { N8nBinaryLoader } from '../../../utils/N8nBinaryLoader';
import { metadataFilterField } from '../../../utils/sharedFields';
// Dependencies needed underneath the hood for the loaders. We add them
// here only to track where what dependency is sued
// import 'd3-dsv'; // for csv
import 'mammoth'; // for docx
import '@gxl/epub-parser'; // for epub
import 'pdf-parse'; // for pdf
import { N8nJsonLoader } from '../../../utils/N8nJsonLoader';
export class DocumentDefaultDataLoader implements INodeType {
description: INodeTypeDescription = {
displayName: 'Default Data Loader',
name: 'documentDefaultDataLoader',
icon: 'file:binary.svg',
group: ['transform'],
version: 1,
description: 'Load data from previous step in the workflow',
defaults: {
name: 'Default Data Loader',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Document Loaders'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.documentdefaultdataloader/',
},
],
},
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputNames: ['Document'],
properties: [
{
displayName:
'This will load data from a previous step in the workflow. <a href="/templates/1962" target="_blank">Example</a>',
name: 'notice',
type: 'notice',
default: '',
},
{
displayName: 'Type of Data',
name: 'dataType',
type: 'options',
default: 'json',
required: true,
noDataExpression: true,
options: [
{
name: 'JSON',
value: 'json',
description: 'Process JSON data from previous step in the workflow',
},
{
name: 'Binary',
value: 'binary',
description: 'Process binary data from previous step in the workflow',
},
],
},
{
displayName: 'Mode',
name: 'jsonMode',
type: 'options',
default: 'allInputData',
required: true,
displayOptions: {
show: {
dataType: ['json'],
},
},
options: [
{
name: 'Load All Input Data',
value: 'allInputData',
description: 'Use all JSON data that flows into the parent agent or chain',
},
{
name: 'Load Specific Data',
value: 'expressionData',
description:
'Load a subset of data, and/or data from any previous step in the workflow',
},
],
},
{
displayName: 'Data Format',
name: 'loader',
type: 'options',
default: 'auto',
required: true,
displayOptions: {
show: {
dataType: ['binary'],
},
},
options: [
{
name: 'Automatically Detect by Mime Type',
value: 'auto',
description: 'Uses the mime type to detect the format',
},
{
name: 'CSV',
value: 'csvLoader',
description: 'Load CSV files',
},
{
name: 'Docx',
value: 'docxLoader',
description: 'Load Docx documents',
},
{
name: 'EPub',
value: 'epubLoader',
description: 'Load EPub files',
},
{
name: 'JSON',
value: 'jsonLoader',
description: 'Load JSON files',
},
{
name: 'PDF',
value: 'pdfLoader',
description: 'Load PDF documents',
},
{
name: 'Text',
value: 'textLoader',
description: 'Load plain text files',
},
],
},
{
displayName: 'Data',
name: 'jsonData',
type: 'string',
typeOptions: {
rows: 6,
},
default: '',
required: true,
description: 'Drag and drop fields from the input pane, or use an expression',
displayOptions: {
show: {
dataType: ['json'],
jsonMode: ['expressionData'],
},
},
},
{
displayName: 'Input Data Field Name',
name: 'binaryDataKey',
type: 'string',
default: 'data',
required: true,
description:
'The name of the field in the agent or chains input that contains the binary file to be processed',
displayOptions: {
show: {
dataType: ['binary'],
},
},
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'JSON Pointers',
name: 'pointers',
type: 'string',
default: '',
description: 'Pointers to extract from JSON, e.g. "/text" or "/text, /meta/title"',
displayOptions: {
show: {
'/loader': ['jsonLoader', 'auto'],
},
},
},
{
displayName: 'CSV Separator',
name: 'separator',
type: 'string',
description: 'Separator to use for CSV',
default: ',',
displayOptions: {
show: {
'/loader': ['csvLoader', 'auto'],
},
},
},
{
displayName: 'CSV Column',
name: 'column',
type: 'string',
default: '',
description: 'Column to extract from CSV',
displayOptions: {
show: {
'/loader': ['csvLoader', 'auto'],
},
},
},
{
displayName: 'Split Pages in PDF',
description: 'Whether to split PDF pages into separate documents',
name: 'splitPages',
type: 'boolean',
default: true,
displayOptions: {
show: {
'/loader': ['pdfLoader', 'auto'],
},
},
},
{
...metadataFilterField,
displayName: 'Metadata',
description:
'Metadata to add to each document. Could be used for filtering during retrieval',
placeholder: 'Add property',
},
],
},
],
};
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
const dataType = this.getNodeParameter('dataType', itemIndex, 'json') as 'json' | 'binary';
const processor =
dataType === 'binary'
? new N8nBinaryLoader(this, 'options.')
: new N8nJsonLoader(this, 'options.');
return {
response: logWrapper(processor, this),
};
}
}

View File

@@ -0,0 +1,3 @@
<svg height="1024" width="768" xmlns="http://www.w3.org/2000/svg">
<path d="M0 960V64h576l192 192v704H0zM704 320L512 128H64v768h640V320zM320 512H128V256h192V512zM256 320h-64v128h64V320zM256 768h64v64H128v-64h64V640h-64v-64h128V768zM512 448h64v64H384v-64h64V320h-64v-64h128V448zM576 832H384V576h192V832zM512 640h-64v128h64V640z" fill="#7D7D87" />
</svg>

After

Width:  |  Height:  |  Size: 355 B

View File

@@ -0,0 +1,125 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import {
NodeConnectionType,
type IExecuteFunctions,
type INodeType,
type INodeTypeDescription,
type SupplyData,
} from 'n8n-workflow';
import { GithubRepoLoader } from 'langchain/document_loaders/web/github';
import type { CharacterTextSplitter } from 'langchain/text_splitter';
import { logWrapper } from '../../../utils/logWrapper';
import { getConnectionHintNoticeField } from '../../../utils/sharedFields';
export class DocumentGithubLoader implements INodeType {
description: INodeTypeDescription = {
displayName: 'GitHub Document Loader',
name: 'documentGithubLoader',
icon: 'file:github.svg',
group: ['transform'],
version: 1,
description: 'Use GitHub data as input to this chain',
defaults: {
name: 'GitHub Document Loader',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Document Loaders'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.documentgithubloader/',
},
],
},
},
credentials: [
{
name: 'githubApi',
required: true,
},
],
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
},
],
inputNames: ['Text Splitter'],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputNames: ['Document'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
{
displayName: 'Repository Link',
name: 'repository',
type: 'string',
default: '',
},
{
displayName: 'Branch',
name: 'branch',
type: 'string',
default: 'main',
},
{
displayName: 'Options',
name: 'additionalOptions',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Recursive',
name: 'recursive',
type: 'boolean',
default: false,
},
{
displayName: 'Ignore Paths',
name: 'recursive',
type: 'string',
description: 'Comma-separated list of paths to ignore, e.g. "docs, src/tests',
default: '',
},
],
},
],
};
async supplyData(this: IExecuteFunctions, itemIndex: number): Promise<SupplyData> {
console.log('Supplying data for Github Document Loader');
const repository = this.getNodeParameter('repository', itemIndex) as string;
const branch = this.getNodeParameter('branch', itemIndex) as string;
const credentials = await this.getCredentials('githubApi');
const { ignorePaths, recursive } = this.getNodeParameter('additionalOptions', 0) as {
recursive: boolean;
ignorePaths: string;
};
const textSplitter = (await this.getInputConnectionData(
NodeConnectionType.AiTextSplitter,
0,
)) as CharacterTextSplitter | undefined;
const docs = new GithubRepoLoader(repository, {
branch,
ignorePaths: (ignorePaths ?? '').split(',').map((p) => p.trim()),
recursive,
accessToken: (credentials.accessToken as string) || '',
});
const loadedDocs = textSplitter ? await docs.loadAndSplit(textSplitter) : await docs.load();
return {
response: logWrapper(loadedDocs, this),
};
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 148.744 150.744" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#a" x=".872" y=".872"/><symbol id="a" overflow="visible"><path d="M73.256 0C32.801 0 0 34.029 0 76.001c0 33.586 20.988 62.069 50.1 72.115 3.663.698 4.999-1.652 4.999-3.656l-.105-14.149c-20.372 4.593-24.677-8.961-24.677-8.961-3.335-8.777-8.133-11.114-8.133-11.114-6.658-4.713.523-4.622.523-4.622 7.355.529 11.227 7.831 11.227 7.831 6.537 11.616 17.151 8.257 21.319 6.309.666-4.901 2.564-8.257 4.65-10.151-16.261-1.919-33.366-8.442-33.366-37.565 0-8.302 2.857-15.075 7.535-20.396-.747-1.929-3.269-9.663.724-20.123 0 0 6.143-2.041 20.145 7.793 5.84-1.692 12.105-2.529 18.314-2.555 6.223.028 12.492.872 18.34 2.564 13.978-9.844 20.128-7.793 20.128-7.793 4.006 10.47 1.483 18.192.733 20.114 4.695 5.32 7.53 12.093 7.53 20.396 0 29.198-17.133 35.627-33.453 37.509 2.639 2.355 4.971 6.977 4.971 14.065l-.098 20.855c0 2.023 1.333 4.388 5.044 3.663 29.091-10.078 50.062-38.561 50.062-72.129C146.512 34.029 113.71 0 73.256 0z" fill="#7D7D87" stroke="none"/></symbol></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,89 @@
/* eslint-disable n8n-nodes-base/node-dirname-against-convention */
import {
NodeConnectionType,
type IExecuteFunctions,
type INodeType,
type INodeTypeDescription,
type SupplyData,
} from 'n8n-workflow';
import { logWrapper } from '../../../utils/logWrapper';
import { N8nJsonLoader } from '../../../utils/N8nJsonLoader';
import { getConnectionHintNoticeField, metadataFilterField } from '../../../utils/sharedFields';
export class DocumentJsonInputLoader implements INodeType {
description: INodeTypeDescription = {
// This node is deprecated and will be removed in the future.
// The functionality was merged with the `DocumentBinaryInputLoader` to `DocumentDefaultDataLoader`
hidden: true,
displayName: 'JSON Input Loader',
name: 'documentJsonInputLoader',
icon: 'file:json.svg',
group: ['transform'],
version: 1,
description: 'Use JSON data from a previous step in the workflow',
defaults: {
name: 'JSON Input Loader',
},
codex: {
categories: ['AI'],
subcategories: {
AI: ['Document Loaders'],
},
resources: {
primaryDocumentation: [
{
url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.documentjsoninputloader/',
},
],
},
},
// eslint-disable-next-line n8n-nodes-base/node-class-description-inputs-wrong-regular-node
inputs: [
{
displayName: 'Text Splitter',
maxConnections: 1,
type: NodeConnectionType.AiTextSplitter,
},
],
inputNames: ['Text Splitter'],
// eslint-disable-next-line n8n-nodes-base/node-class-description-outputs-wrong
outputs: [NodeConnectionType.AiDocument],
outputNames: ['Document'],
properties: [
getConnectionHintNoticeField([NodeConnectionType.AiVectorStore]),
{
displayName: 'Pointers',
name: 'pointers',
type: 'string',
default: '',
description: 'Pointers to extract from JSON, e.g. "/text" or "/text, /meta/title"',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
...metadataFilterField,
displayName: 'Metadata',
description:
'Metadata to add to each document. Could be used for filtering during retrieval',
placeholder: 'Add property',
},
],
},
],
};
async supplyData(this: IExecuteFunctions): Promise<SupplyData> {
this.logger.verbose('Supply Data for JSON Input Loader');
const processor = new N8nJsonLoader(this);
return {
response: logWrapper(processor, this),
};
}
}

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#7D7D87" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 58 58" xml:space="preserve">
<g>
<path d="M50.949,12.187l-1.361-1.361l-9.504-9.505c-0.001-0.001-0.001-0.001-0.002-0.001l-0.77-0.771
C38.957,0.195,38.486,0,37.985,0H8.963C7.776,0,6.5,0.916,6.5,2.926V39v16.537V56c0,0.837,0.841,1.652,1.836,1.909
c0.051,0.014,0.1,0.033,0.152,0.043C8.644,57.983,8.803,58,8.963,58h40.074c0.16,0,0.319-0.017,0.475-0.048
c0.052-0.01,0.101-0.029,0.152-0.043C50.659,57.652,51.5,56.837,51.5,56v-0.463V39V13.978C51.5,13.211,51.407,12.644,50.949,12.187
z M39.5,3.565L47.935,12H39.5V3.565z M8.963,56c-0.071,0-0.135-0.025-0.198-0.049C8.61,55.877,8.5,55.721,8.5,55.537V41h41v14.537
c0,0.184-0.11,0.34-0.265,0.414C49.172,55.975,49.108,56,49.037,56H8.963z M8.5,39V2.926C8.5,2.709,8.533,2,8.963,2h28.595
C37.525,2.126,37.5,2.256,37.5,2.391V13.78c-0.532-0.48-1.229-0.78-2-0.78c-0.553,0-1,0.448-1,1s0.447,1,1,1c0.552,0,1,0.449,1,1v4
c0,1.2,0.542,2.266,1.382,3c-0.84,0.734-1.382,1.8-1.382,3v4c0,0.551-0.448,1-1,1c-0.553,0-1,0.448-1,1s0.447,1,1,1
c1.654,0,3-1.346,3-3v-4c0-1.103,0.897-2,2-2c0.553,0,1-0.448,1-1s-0.447-1-1-1c-1.103,0-2-0.897-2-2v-4
c0-0.771-0.301-1.468-0.78-2h11.389c0.135,0,0.265-0.025,0.391-0.058c0,0.015,0.001,0.021,0.001,0.036V39H8.5z"/>
<path d="M16.354,51.43c-0.019,0.446-0.171,0.764-0.458,0.95s-0.672,0.28-1.155,0.28c-0.191,0-0.396-0.022-0.615-0.068
s-0.429-0.098-0.629-0.157s-0.385-0.123-0.554-0.191s-0.299-0.135-0.39-0.198l-0.697,1.107c0.183,0.137,0.405,0.26,0.67,0.369
s0.54,0.207,0.827,0.294s0.565,0.15,0.834,0.191s0.504,0.062,0.704,0.062c0.401,0,0.791-0.039,1.169-0.116
c0.378-0.077,0.713-0.214,1.005-0.41s0.524-0.456,0.697-0.779s0.26-0.723,0.26-1.196v-7.848h-1.668V51.43z"/>
<path d="M25.083,49.064c-0.314-0.228-0.654-0.422-1.019-0.581s-0.702-0.323-1.012-0.492s-0.569-0.364-0.779-0.588
s-0.314-0.518-0.314-0.882c0-0.146,0.036-0.299,0.109-0.458s0.173-0.303,0.301-0.431s0.273-0.234,0.438-0.321
s0.337-0.139,0.52-0.157c0.328-0.027,0.597-0.032,0.807-0.014s0.378,0.05,0.506,0.096s0.226,0.091,0.294,0.137
s0.13,0.082,0.185,0.109c0.009-0.009,0.036-0.055,0.082-0.137s0.101-0.185,0.164-0.308s0.132-0.255,0.205-0.396
s0.137-0.271,0.191-0.39c-0.265-0.173-0.61-0.299-1.039-0.376s-0.853-0.116-1.271-0.116c-0.41,0-0.8,0.063-1.169,0.191
s-0.692,0.313-0.971,0.554s-0.499,0.535-0.663,0.882S20.4,46.13,20.4,46.576c0,0.492,0.104,0.902,0.314,1.23
s0.474,0.613,0.793,0.854s0.661,0.451,1.025,0.629s0.704,0.355,1.019,0.533s0.576,0.376,0.786,0.595s0.314,0.483,0.314,0.793
c0,0.511-0.148,0.896-0.444,1.155s-0.723,0.39-1.278,0.39c-0.183,0-0.378-0.019-0.588-0.055s-0.419-0.084-0.629-0.144
s-0.412-0.123-0.608-0.191s-0.357-0.139-0.485-0.212l-0.287,1.176c0.155,0.137,0.34,0.253,0.554,0.349s0.439,0.171,0.677,0.226
c0.237,0.055,0.472,0.094,0.704,0.116s0.458,0.034,0.677,0.034c0.511,0,0.966-0.077,1.367-0.232s0.738-0.362,1.012-0.622
s0.485-0.561,0.636-0.902s0.226-0.695,0.226-1.06c0-0.538-0.104-0.978-0.314-1.319S25.397,49.292,25.083,49.064z"/>
<path d="M34.872,45.072c-0.378-0.429-0.82-0.754-1.326-0.978s-1.06-0.335-1.661-0.335s-1.155,0.111-1.661,0.335
s-0.948,0.549-1.326,0.978s-0.675,0.964-0.889,1.606s-0.321,1.388-0.321,2.235s0.107,1.595,0.321,2.242s0.511,1.185,0.889,1.613
s0.82,0.752,1.326,0.971s1.06,0.328,1.661,0.328s1.155-0.109,1.661-0.328s0.948-0.542,1.326-0.971s0.675-0.966,0.889-1.613
s0.321-1.395,0.321-2.242s-0.107-1.593-0.321-2.235S35.25,45.501,34.872,45.072z M34.195,50.698
c-0.137,0.487-0.326,0.882-0.567,1.183s-0.515,0.518-0.82,0.649s-0.627,0.198-0.964,0.198c-0.328,0-0.641-0.07-0.937-0.212
s-0.561-0.364-0.793-0.67s-0.415-0.699-0.547-1.183s-0.203-1.066-0.212-1.75c0.009-0.702,0.082-1.294,0.219-1.777
c0.137-0.483,0.326-0.877,0.567-1.183s0.515-0.521,0.82-0.649s0.627-0.191,0.964-0.191c0.328,0,0.641,0.068,0.937,0.205
s0.561,0.36,0.793,0.67s0.415,0.704,0.547,1.183s0.203,1.06,0.212,1.743C34.405,49.616,34.332,50.211,34.195,50.698z"/>
<polygon points="44.012,50.869 40.061,43.924 38.393,43.924 38.393,54 40.061,54 40.061,47.055 44.012,54 45.68,54 45.68,43.924
44.012,43.924 "/>
<path d="M20.5,20v-4c0-0.551,0.448-1,1-1c0.553,0,1-0.448,1-1s-0.447-1-1-1c-1.654,0-3,1.346-3,3v4c0,1.103-0.897,2-2,2
c-0.553,0-1,0.448-1,1s0.447,1,1,1c1.103,0,2,0.897,2,2v4c0,1.654,1.346,3,3,3c0.553,0,1-0.448,1-1s-0.447-1-1-1
c-0.552,0-1-0.449-1-1v-4c0-1.2-0.542-2.266-1.382-3C19.958,22.266,20.5,21.2,20.5,20z"/>
<circle cx="28.5" cy="19.5" r="1.5"/>
<path d="M28.5,25c-0.553,0-1,0.448-1,1v3c0,0.552,0.447,1,1,1s1-0.448,1-1v-3C29.5,25.448,29.053,25,28.5,25z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB